(function ($) {
    var lm = { "config": {}, "container": {}, "controls": {}, "errors": {}, "items": {}, "utils": {} };
    lm.utils.F = function () {
    };
    lm.utils.extend = function (subClass, superClass) {
        subClass.prototype = lm.utils.createObject(superClass.prototype);
        subClass.prototype.contructor = subClass;
    };
    lm.utils.createObject = function (prototype) {
        if (typeof Object.create === 'function') {
            return Object.create(prototype);
        } else {
            lm.utils.F.prototype = prototype;
            return new lm.utils.F();
        }
    };
    lm.utils.objectKeys = function (object) {
        var keys, key;
        if (typeof Object.keys === 'function') {
            return Object.keys(object);
        } else {
            keys = [];
            for (key in object) {
                keys.push(key);
            }
            return keys;
        }
    };
    lm.utils.getHashValue = function (key) {
        var matches = location.hash.match(new RegExp(key + '=([^&]*)'));
        return matches ? matches[1] : null;
    };
    lm.utils.getQueryStringParam = function (param) {
        if (window.location.hash) {
            return lm.utils.getHashValue(param);
        } else if (!window.location.search) {
            return null;
        }
        var keyValuePairs = window.location.search.substr(1).split('&'),
            params = {},
            pair,
            i;
        for (i = 0; i < keyValuePairs.length; i++) {
            pair = keyValuePairs[i].split('=');
            params[pair[0]] = pair[1];
        }
        return params[param] || null;
    };
    lm.utils.copy = function (target, source) {
        for (var key in source) {
            target[key] = source[key];
        }
        return target;
    };
	/**
	 * This is based on Paul Irish's shim, but looks quite odd in comparison. Why?
	 * Because
	 * a) it shouldn't affect the global requestAnimationFrame function
	 * b) it shouldn't pass on the time that has passed
	 *
	 * @param   {Function} fn
	 *
	 * @returns {void}
	 */
    lm.utils.animFrame = function (fn) {
        return (window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            function (callback) {
                window.setTimeout(callback, 1000 / 60);
            })(function () {
                fn();
            });
    };
    lm.utils.indexOf = function (needle, haystack) {
        if (!(haystack instanceof Array)) {
            throw new Error('Haystack is not an Array');
        }
        if (haystack.indexOf) {
            return haystack.indexOf(needle);
        } else {
            for (var i = 0; i < haystack.length; i++) {
                if (haystack[i] === needle) {
                    return i;
                }
            }
            return -1;
        }
    };
    if (typeof /./ != 'function' && typeof Int8Array != 'object') {
        lm.utils.isFunction = function (obj) {
            return typeof obj == 'function' || false;
        };
    } else {
        lm.utils.isFunction = function (obj) {
            return toString.call(obj) === '[object Function]';
        };
    }
    lm.utils.fnBind = function (fn, context, boundArgs) {
        if (Function.prototype.bind !== undefined) {
            return Function.prototype.bind.apply(fn, [context].concat(boundArgs || []));
        }
        var bound = function () {
            // Join the already applied arguments to the now called ones (after converting to an array again).
            var args = (boundArgs || []).concat(Array.prototype.slice.call(arguments, 0));
            // If not being called as a constructor
            if (!(this instanceof bound)) {
                // return the result of the function called bound to target and partially applied.
                return fn.apply(context, args);
            }
            // If being called as a constructor, apply the function bound to self.
            fn.apply(this, args);
        };
        // Attach the prototype of the function to our newly created function.
        bound.prototype = fn.prototype;
        return bound;
    };
    lm.utils.removeFromArray = function (item, array) {
        var index = lm.utils.indexOf(item, array);
        if (index === -1) {
            throw new Error('Can\'t remove item from array. Item is not in the array');
        }
        array.splice(index, 1);
    };
    lm.utils.now = function () {
        if (typeof Date.now === 'function') {
            return Date.now();
        } else {
            return (new Date()).getTime();
        }
    };
    lm.utils.getUniqueId = function () {
        return (Math.random() * 1000000000000000)
            .toString(36)
            .replace('.', '');
    };
	/**
	 * A basic XSS filter. It is ultimately up to the
	 * implementing developer to make sure their particular
	 * applications and usecases are save from cross site scripting attacks
	 *
	 * @param   {String} input
	 * @param    {Boolean} keepTags
	 *
	 * @returns {String} filtered input
	 */
    lm.utils.filterXss = function (input, keepTags) {
        var output = input
            .replace(/javascript/gi, 'javascript')
            .replace(/expression/gi, 'expression')
            .replace(/onload/gi, 'onload')
            .replace(/script/gi, 'script')
            .replace(/onerror/gi, 'onerror');
        if (keepTags === true) {
            return output;
        } else {
            return output
                .replace(/>/g, '>')
                .replace(/]+)>)/ig, ''));
    };
	/**
	 * A generic and very fast EventEmitter
	 * implementation. On top of emitting the
	 * actual event it emits an
	 *
	 * lm.utils.EventEmitter.ALL_EVENT
	 *
	 * event for every event triggered. This allows
	 * to hook into it and proxy events forwards
	 *
	 * @constructor
	 */
    lm.utils.EventEmitter = function () {
        this._mSubscriptions = {};
        this._mSubscriptions[lm.utils.EventEmitter.ALL_EVENT] = [];
		/**
		 * Listen for events
		 *
		 * @param   {String} sEvent    The name of the event to listen to
		 * @param   {Function} fCallback The callback to execute when the event occurs
		 * @param   {[Object]} oContext The value of the this pointer within the callback function
		 *
		 * @returns {void}
		 */
        this.on = function (sEvent, fCallback, oContext) {
            if (!lm.utils.isFunction(fCallback)) {
                throw new Error('Tried to listen to event ' + sEvent + ' with non-function callback ' + fCallback);
            }
            if (!this._mSubscriptions[sEvent]) {
                this._mSubscriptions[sEvent] = [];
            }
            this._mSubscriptions[sEvent].push({ fn: fCallback, ctx: oContext });
        };
		/**
		 * Emit an event and notify listeners
		 *
		 * @param   {String} sEvent The name of the event
		 * @param    {Mixed}  various additional arguments that will be passed to the listener
		 *
		 * @returns {void}
		 */
        this.emit = function (sEvent) {
            var i, ctx, args;
            args = Array.prototype.slice.call(arguments, 1);
            var subs = this._mSubscriptions[sEvent];
            if (subs) {
                subs = subs.slice();
                for (i = 0; i < subs.length; i++) {
                    ctx = subs[i].ctx || {};
                    subs[i].fn.apply(ctx, args);
                }
            }
            args.unshift(sEvent);
            var allEventSubs = this._mSubscriptions[lm.utils.EventEmitter.ALL_EVENT].slice()
            for (i = 0; i < allEventSubs.length; i++) {
                ctx = allEventSubs[i].ctx || {};
                allEventSubs[i].fn.apply(ctx, args);
            }
        };
		/**
		 * Removes a listener for an event, or all listeners if no callback and context is provided.
		 *
		 * @param   {String} sEvent    The name of the event
		 * @param   {Function} fCallback The previously registered callback method (optional)
		 * @param   {Object} oContext  The previously registered context (optional)
		 *
		 * @returns {void}
		 */
        this.unbind = function (sEvent, fCallback, oContext) {
            if (!this._mSubscriptions[sEvent]) {
                throw new Error('No subscribtions to unsubscribe for event ' + sEvent);
            }
            var i, bUnbound = false;
            for (i = 0; i < this._mSubscriptions[sEvent].length; i++) {
                if
                    (
                    (!fCallback || this._mSubscriptions[sEvent][i].fn === fCallback) &&
                    (!oContext || oContext === this._mSubscriptions[sEvent][i].ctx)
                ) {
                    this._mSubscriptions[sEvent].splice(i, 1);
                    bUnbound = true;
                }
            }
            if (bUnbound === false) {
                throw new Error('Nothing to unbind for ' + sEvent);
            }
        };
		/**
		 * Alias for unbind
		 */
        this.off = this.unbind;
		/**
		 * Alias for emit
		 */
        this.trigger = this.emit;
    };
	/**
	 * The name of the event that's triggered for every other event
	 *
	 * usage
	 *
	 * myEmitter.on( lm.utils.EventEmitter.ALL_EVENT, function( eventName, argsArray ){
	 * 	//do stuff
	 * });
	 *
	 * @type {String}
	 */
    lm.utils.EventEmitter.ALL_EVENT = '__all';
    lm.utils.DragListener = function (eElement, nButtonCode) {
        lm.utils.EventEmitter.call(this);
        this._eElement = $(eElement);
        this._oDocument = $(document);
        this._eBody = $(document.body);
        this._nButtonCode = nButtonCode || 0;
		/**
		 * The delay after which to start the drag in milliseconds
		 */
        this._nDelay = 200;
		/**
		 * The distance the mouse needs to be moved to qualify as a drag
		 */
        this._nDistance = 10;//TODO - works better with delay only
        this._nX = 0;
        this._nY = 0;
        this._nOriginalX = 0;
        this._nOriginalY = 0;
        this._bDragging = false;
        this._fMove = lm.utils.fnBind(this.onMouseMove, this);
        this._fUp = lm.utils.fnBind(this.onMouseUp, this);
        this._fDown = lm.utils.fnBind(this.onMouseDown, this);
        this._eElement.on('mousedown touchstart', this._fDown);
    };
    lm.utils.DragListener.timeout = null;
    lm.utils.copy(lm.utils.DragListener.prototype, {
        destroy: function () {
            this._eElement.unbind('mousedown touchstart', this._fDown);
            this._oDocument.unbind('mouseup touchend', this._fUp);
            this._eElement = null;
            this._oDocument = null;
            this._eBody = null;
        },
        onMouseDown: function (oEvent) {
            oEvent.preventDefault();
            if (oEvent.button == 0 || oEvent.type === "touchstart") {
                var coordinates = this._getCoordinates(oEvent);
                this._nOriginalX = coordinates.x;
                this._nOriginalY = coordinates.y;
                this._oDocument.on('mousemove touchmove', this._fMove);
                this._oDocument.one('mouseup touchend', this._fUp);
                this._timeout = setTimeout(lm.utils.fnBind(this._startDrag, this), this._nDelay);
            }
        },
        onMouseMove: function (oEvent) {
            if (this._timeout != null) {
                oEvent.preventDefault();
                var coordinates = this._getCoordinates(oEvent);
                this._nX = coordinates.x - this._nOriginalX;
                this._nY = coordinates.y - this._nOriginalY;
                if (this._bDragging === false) {
                    if (
                        Math.abs(this._nX) > this._nDistance ||
                        Math.abs(this._nY) > this._nDistance
                    ) {
                        clearTimeout(this._timeout);
                        this._startDrag();
                    }
                }
                if (this._bDragging) {
                    this.emit('drag', this._nX, this._nY, oEvent);
                }
            }
        },
        onMouseUp: function (oEvent) {
            if (this._timeout != null) {
                clearTimeout(this._timeout);
                this._eBody.removeClass('lm_dragging');
                this._eElement.removeClass('lm_dragging');
                this._oDocument.find('iframe').css('pointer-events', '');
                this._oDocument.unbind('mousemove touchmove', this._fMove);
                this._oDocument.unbind('mouseup touchend', this._fUp);
                if (this._bDragging === true) {
                    this._bDragging = false;
                    this.emit('dragStop', oEvent, this._nOriginalX + this._nX);
                }
            }
        },
        _startDrag: function () {
            this._bDragging = true;
            this._eBody.addClass('lm_dragging');
            this._eElement.addClass('lm_dragging');
            this._oDocument.find('iframe').css('pointer-events', 'none');
            this.emit('dragStart', this._nOriginalX, this._nOriginalY);
        },
        _getCoordinates: function (event) {
            event = event.originalEvent && event.originalEvent.touches ? event.originalEvent.touches[0] : event;
            return {
                x: event.pageX,
                y: event.pageY
            };
        }
    });
	/**
	 * The main class that will be exposed as GoldenLayout.
	 *
	 * @public
	 * @constructor
	 * @param {GoldenLayout config} config
	 * @param {[DOM element container]} container Can be a jQuery selector string or a Dom element. Defaults to body
	 *
	 * @returns {VOID}
	 */
    lm.LayoutManager = function (config, container) {
        if (!$ || typeof $.noConflict !== 'function') {
            var errorMsg = 'jQuery is missing as dependency for GoldenLayout. ';
            errorMsg += 'Please either expose $ on GoldenLayout\'s scope (e.g. window) or add "jquery" to ';
            errorMsg += 'your paths when using RequireJS/AMD';
            throw new Error(errorMsg);
        }
        lm.utils.EventEmitter.call(this);
        this.isInitialised = false;
        this._isFullPage = false;
        this._resizeTimeoutId = null;
        this._components = { 'lm-react-component': lm.utils.ReactComponentHandler };
        this._itemAreas = [];
        this._resizeFunction = lm.utils.fnBind(this._onResize, this);
        this._unloadFunction = lm.utils.fnBind(this._onUnload, this);
        this._maximisedItem = null;
        this._maximisePlaceholder = $('
');
        this._creationTimeoutPassed = false;
        this._subWindowsCreated = false;
        this._dragSources = [];
        this._updatingColumnsResponsive = false;
        this._firstLoad = true;
        this.width = null;
        this.height = null;
        this.root = null;
        this.openPopouts = [];
        this.selectedItem = null;
        this.isSubWindow = false;
        this.eventHub = new lm.utils.EventHub(this);
        this.config = this._createConfig(config);
        this.container = container;
        this.dropTargetIndicator = null;
        this.transitionIndicator = null;
        this.tabDropPlaceholder = $('');
        if (this.isSubWindow === true) {
            $('body').css('visibility', 'hidden');
        }
        this._typeToItem = {
            'column': lm.utils.fnBind(lm.items.RowOrColumn, this, [true]),
            'row': lm.utils.fnBind(lm.items.RowOrColumn, this, [false]),
            'stack': lm.items.Stack,
            'component': lm.items.Component
        };
    };
	/**
	 * Hook that allows to access private classes
	 */
    lm.LayoutManager.__lm = lm;
	/**
	 * Takes a GoldenLayout configuration object and
	 * replaces its keys and values recursively with
	 * one letter codes
	 *
	 * @static
	 * @public
	 * @param   {Object} config A GoldenLayout config object
	 *
	 * @returns {Object} minified config
	 */
    lm.LayoutManager.minifyConfig = function (config) {
        return (new lm.utils.ConfigMinifier()).minifyConfig(config);
    };
	/**
	 * Takes a configuration Object that was previously minified
	 * using minifyConfig and returns its original version
	 *
	 * @static
	 * @public
	 * @param   {Object} minifiedConfig
	 *
	 * @returns {Object} the original configuration
	 */
    lm.LayoutManager.unminifyConfig = function (config) {
        return (new lm.utils.ConfigMinifier()).unminifyConfig(config);
    };
    lm.utils.copy(lm.LayoutManager.prototype, {
		/**
		 * Register a component with the layout manager. If a configuration node
		 * of type component is reached it will look up componentName and create the
		 * associated component
		 *
		 *  {
		 *		type: "component",
		 *		componentName: "EquityNewsFeed",
		 *		componentState: { "feedTopic": "us-bluechips" }
		 *  }
		 *
		 * @public
		 * @param   {String} name
		 * @param   {Function} constructor
		 *
		 * @returns {void}
		 */
        registerComponent: function (name, constructor) {
            if (typeof constructor !== 'function') {
                throw new Error('Please register a constructor function');
            }
            if (this._components[name] !== undefined) {
                throw new Error('Component ' + name + ' is already registered');
            }
            this._components[name] = constructor;
        },
		/**
		 * Creates a layout configuration object based on the the current state
		 *
		 * @public
		 * @returns {Object} GoldenLayout configuration
		 */
        toConfig: function (root) {
            var config, next, i;
            if (this.isInitialised === false) {
                throw new Error('Can\'t create config, layout not yet initialised');
            }
            if (root && !(root instanceof lm.items.AbstractContentItem)) {
                throw new Error('Root must be a ContentItem');
            }
			/*
			 * settings & labels
			 */
            config = {
                settings: lm.utils.copy({}, this.config.settings),
                dimensions: lm.utils.copy({}, this.config.dimensions),
                labels: lm.utils.copy({}, this.config.labels)
            };
			/*
			 * Content
			 */
            config.content = [];
            next = function (configNode, item) {
                var key, i;
                for (key in item.config) {
                    if (key !== 'content') {
                        configNode[key] = item.config[key];
                    }
                }
                if (item.contentItems.length) {
                    configNode.content = [];
                    for (i = 0; i < item.contentItems.length; i++) {
                        configNode.content[i] = {};
                        next(configNode.content[i], item.contentItems[i]);
                    }
                }
            };
            if (root) {
                next(config, { contentItems: [root] });
            } else {
                next(config, this.root);
            }
			/*
			 * Retrieve config for subwindows
			 */
            this._$reconcilePopoutWindows();
            config.openPopouts = [];
            for (i = 0; i < this.openPopouts.length; i++) {
                config.openPopouts.push(this.openPopouts[i].toConfig());
            }
			/*
			 * Add maximised item
			 */
            config.maximisedItemId = this._maximisedItem ? '__glMaximised' : null;
            return config;
        },
		/**
		 * Returns a previously registered component
		 *
		 * @public
		 * @param   {String} name The name used
		 *
		 * @returns {Function}
		 */
        getComponent: function (name) {
            if (this._components[name] === undefined) {
                throw new lm.errors.ConfigurationError('Unknown component "' + name + '"');
            }
            return this._components[name];
        },
		/**
		 * Creates the actual layout. Must be called after all initial components
		 * are registered. Recurses through the configuration and sets up
		 * the item tree.
		 *
		 * If called before the document is ready it adds itself as a listener
		 * to the document.ready event
		 *
		 * @public
		 *
		 * @returns {void}
		 */
        init: function () {
			/**
			 * Create the popout windows straight away. If popouts are blocked
			 * an error is thrown on the same 'thread' rather than a timeout and can
			 * be caught. This also prevents any further initilisation from taking place.
			 */
            if (this._subWindowsCreated === false) {
                this._createSubWindows();
                this._subWindowsCreated = true;
            }
			/**
			 * If the document isn't ready yet, wait for it.
			 */
            if (document.readyState === 'loading' || document.body === null) {
                $(document).ready(lm.utils.fnBind(this.init, this));
                return;
            }
			/**
			 * If this is a subwindow, wait a few milliseconds for the original
			 * page's js calls to be executed, then replace the bodies content
			 * with GoldenLayout
			 */
            if (this.isSubWindow === true && this._creationTimeoutPassed === false) {
                setTimeout(lm.utils.fnBind(this.init, this), 7);
                this._creationTimeoutPassed = true;
                return;
            }
            if (this.isSubWindow === true) {
                this._adjustToWindowMode();
            }
            this._setContainer();
            this.dropTargetIndicator = new lm.controls.DropTargetIndicator(this.container);
            this.transitionIndicator = new lm.controls.TransitionIndicator();
            this.updateSize();
            this._create(this.config);
            this._bindEvents();
            this.isInitialised = true;
            this._adjustColumnsResponsive();
            this.emit('initialised');
        },
		/**
		 * Updates the layout managers size
		 *
		 * @public
		 * @param   {[int]} width  height in pixels
		 * @param   {[int]} height width in pixels
		 *
		 * @returns {void}
		 */
        updateSize: function (width, height) {
            if (arguments.length === 2) {
                this.width = width;
                this.height = height;
            } else {
                this.width = this.container.width();
                this.height = this.container.height();
            }
            if (this.isInitialised === true) {
                this.root.callDownwards('setSize', [this.width, this.height]);
                if (this._maximisedItem) {
                    this._maximisedItem.element.width(this.container.width());
                    this._maximisedItem.element.height(this.container.height());
                    this._maximisedItem.callDownwards('setSize');
                }
                this._adjustColumnsResponsive();
            }
        },
		/**
		 * Destroys the LayoutManager instance itself as well as every ContentItem
		 * within it. After this is called nothing should be left of the LayoutManager.
		 *
		 * @public
		 * @returns {void}
		 */
        destroy: function () {
            if (this.isInitialised === false) {
                return;
            }
            this._onUnload();
            $(window).off('resize', this._resizeFunction);
            $(window).off('unload beforeunload', this._unloadFunction);
            this.root.callDownwards('_$destroy', [], true);
            this.root.contentItems = [];
            this.tabDropPlaceholder.remove();
            this.dropTargetIndicator.destroy();
            this.transitionIndicator.destroy();
            this.eventHub.destroy();
            this._dragSources.forEach(function (dragSource) {
                dragSource._dragListener.destroy();
                dragSource._element = null;
                dragSource._itemConfig = null;
                dragSource._dragListener = null;
            });
            this._dragSources = [];
        },
		/**
		 * Recursively creates new item tree structures based on a provided
		 * ItemConfiguration object
		 *
		 * @public
		 * @param   {Object} config ItemConfig
		 * @param   {[ContentItem]} parent The item the newly created item should be a child of
		 *
		 * @returns {lm.items.ContentItem}
		 */
        createContentItem: function (config, parent) {
            var typeErrorMsg, contentItem;
            if (typeof config.type !== 'string') {
                throw new lm.errors.ConfigurationError('Missing parameter \'type\'', config);
            }
            if (config.type === 'react-component') {
                config.type = 'component';
                config.componentName = 'lm-react-component';
            }
            if (!this._typeToItem[config.type]) {
                typeErrorMsg = 'Unknown type \'' + config.type + '\'. ' +
                    'Valid types are ' + lm.utils.objectKeys(this._typeToItem).join(',');
                throw new lm.errors.ConfigurationError(typeErrorMsg);
            }
			/**
			 * We add an additional stack around every component that's not within a stack anyways.
			 */
            if (
                // If this is a component
                config.type === 'component' &&
                // and it's not already within a stack
                !(parent instanceof lm.items.Stack) &&
                // and we have a parent
                !!parent &&
                // and it's not the topmost item in a new window
                !(this.isSubWindow === true && parent instanceof lm.items.Root)
            ) {
                config = {
                    type: 'stack',
                    width: config.width,
                    height: config.height,
                    content: [config]
                };
            }
            contentItem = new this._typeToItem[config.type](this, config, parent);
            return contentItem;
        },
		/**
		 * Creates a popout window with the specified content and dimensions
		 *
		 * @param   {Object|lm.itemsAbstractContentItem} configOrContentItem
		 * @param   {[Object]} dimensions A map with width, height, left and top
		 * @param    {[String]} parentId the id of the element this item will be appended to
		 *                             when popIn is called
		 * @param    {[Number]} indexInParent The position of this item within its parent element
	
		 * @returns {lm.controls.BrowserPopout}
		 */
        createPopout: function (configOrContentItem, dimensions, parentId, indexInParent) {
            var config = configOrContentItem,
                isItem = configOrContentItem instanceof lm.items.AbstractContentItem,
                self = this,
                windowLeft,
                windowTop,
                offset,
                parent,
                child,
                browserPopout;
            parentId = parentId || null;
            if (isItem) {
                config = this.toConfig(configOrContentItem).content;
                parentId = lm.utils.getUniqueId();
				/**
				 * If the item is the only component within a stack or for some
				 * other reason the only child of its parent the parent will be destroyed
				 * when the child is removed.
				 *
				 * In order to support this we move up the tree until we find something
				 * that will remain after the item is being popped out
				 */
                parent = configOrContentItem.parent;
                child = configOrContentItem;
                while (parent.contentItems.length === 1 && !parent.isRoot) {
                    parent = parent.parent;
                    child = child.parent;
                }
                parent.addId(parentId);
                if (isNaN(indexInParent)) {
                    indexInParent = lm.utils.indexOf(child, parent.contentItems);
                }
            } else {
                if (!(config instanceof Array)) {
                    config = [config];
                }
            }
            if (!dimensions && isItem) {
                windowLeft = window.screenX || window.screenLeft;
                windowTop = window.screenY || window.screenTop;
                offset = configOrContentItem.element.offset();
                dimensions = {
                    left: windowLeft + offset.left,
                    top: windowTop + offset.top,
                    width: configOrContentItem.element.width(),
                    height: configOrContentItem.element.height()
                };
            }
            if (!dimensions && !isItem) {
                dimensions = {
                    left: window.screenX || window.screenLeft + 20,
                    top: window.screenY || window.screenTop + 20,
                    width: 500,
                    height: 309
                };
            }
            if (isItem) {
                configOrContentItem.remove();
            }
            browserPopout = new lm.controls.BrowserPopout(config, dimensions, parentId, indexInParent, this);
            browserPopout.on('initialised', function () {
                self.emit('windowOpened', browserPopout);
            });
            browserPopout.on('closed', function () {
                self._$reconcilePopoutWindows();
            });
            this.openPopouts.push(browserPopout);
            return browserPopout;
        },
		/**
		 * Attaches DragListener to any given DOM element
		 * and turns it into a way of creating new ContentItems
		 * by 'dragging' the DOM element into the layout
		 *
		 * @param   {jQuery DOM element} element
		 * @param   {Object|Function} itemConfig for the new item to be created, or a function which will provide it
		 *
		 * @returns {void}
		 */
        createDragSource: function (element, itemConfig) {
            this.config.settings.constrainDragToContainer = false;
            var dragSource = new lm.controls.DragSource($(element), itemConfig, this);
            this._dragSources.push(dragSource);
            return dragSource;
        },
		/**
		 * Programmatically selects an item. This deselects
		 * the currently selected item, selects the specified item
		 * and emits a selectionChanged event
		 *
		 * @param   {lm.item.AbstractContentItem} item#
		 * @param   {[Boolean]} _$silent Wheather to notify the item of its selection
		 * @event    selectionChanged
		 *
		 * @returns {VOID}
		 */
        selectItem: function (item, _$silent) {
            if (this.config.settings.selectionEnabled !== true) {
                throw new Error('Please set selectionEnabled to true to use this feature');
            }
            if (item === this.selectedItem) {
                return;
            }
            if (this.selectedItem !== null) {
                this.selectedItem.deselect();
            }
            if (item && _$silent !== true) {
                item.select();
            }
            this.selectedItem = item;
            this.emit('selectionChanged', item);
        },
		/*************************
		 * PACKAGE PRIVATE
		 *************************/
        _$maximiseItem: function (contentItem) {
            if (this._maximisedItem !== null) {
                this._$minimiseItem(this._maximisedItem);
            }
            this._maximisedItem = contentItem;
            this._maximisedItem.addId('__glMaximised');
            contentItem.element.addClass('lm_maximised');
            contentItem.element.after(this._maximisePlaceholder);
            this.root.element.prepend(contentItem.element);
            contentItem.element.width(this.container.width());
            contentItem.element.height(this.container.height());
            contentItem.callDownwards('setSize');
            this._maximisedItem.emit('maximised');
            this.emit('stateChanged');
        },
        _$minimiseItem: function (contentItem) {
            contentItem.element.removeClass('lm_maximised');
            contentItem.removeId('__glMaximised');
            this._maximisePlaceholder.after(contentItem.element);
            this._maximisePlaceholder.remove();
            contentItem.parent.callDownwards('setSize');
            this._maximisedItem = null;
            contentItem.emit('minimised');
            this.emit('stateChanged');
        },
		/**
		 * This method is used to get around sandboxed iframe restrictions.
		 * If 'allow-top-navigation' is not specified in the iframe's 'sandbox' attribute
		 * (as is the case with codepens) the parent window is forbidden from calling certain
		 * methods on the child, such as window.close() or setting document.location.href.
		 *
		 * This prevented GoldenLayout popouts from popping in in codepens. The fix is to call
		 * _$closeWindow on the child window's gl instance which (after a timeout to disconnect
		 * the invoking method from the close call) closes itself.
		 *
		 * @packagePrivate
		 *
		 * @returns {void}
		 */
        _$closeWindow: function () {
            window.setTimeout(function () {
                window.close();
            }, 1);
        },
        _$getArea: function (x, y) {
            var i, area, smallestSurface = Infinity, mathingArea = null;
            for (i = 0; i < this._itemAreas.length; i++) {
                area = this._itemAreas[i];
                if (
                    x > area.x1 &&
                    x < area.x2 &&
                    y > area.y1 &&
                    y < area.y2 &&
                    smallestSurface > area.surface
                ) {
                    smallestSurface = area.surface;
                    mathingArea = area;
                }
            }
            return mathingArea;
        },
        _$createRootItemAreas: function () {
            var areaSize = 50;
            var sides = { y2: 0, x2: 0, y1: 'y2', x1: 'x2' };
            for (var side in sides) {
                var area = this.root._$getArea();
                area.side = side;
                if (sides[side])
                    area[side] = area[sides[side]] - areaSize;
                else
                    area[side] = areaSize;
                area.surface = (area.x2 - area.x1) * (area.y2 - area.y1);
                this._itemAreas.push(area);
            }
        },
        _$calculateItemAreas: function () {
            var i, area, allContentItems = this._getAllContentItems();
            this._itemAreas = [];
			/**
			 * If the last item is dragged out, highlight the entire container size to
			 * allow to re-drop it. allContentItems[ 0 ] === this.root at this point
			 *
			 * Don't include root into the possible drop areas though otherwise since it
			 * will used for every gap in the layout, e.g. splitters
			 */
            if (allContentItems.length === 1) {
                this._itemAreas.push(this.root._$getArea());
                return;
            }
            this._$createRootItemAreas();
            for (i = 0; i < allContentItems.length; i++) {
                if (!(allContentItems[i].isStack)) {
                    continue;
                }
                area = allContentItems[i]._$getArea();
                if (area === null) {
                    continue;
                } else if (area instanceof Array) {
                    this._itemAreas = this._itemAreas.concat(area);
                } else {
                    this._itemAreas.push(area);
                    var header = {};
                    lm.utils.copy(header, area);
                    lm.utils.copy(header, area.contentItem._contentAreaDimensions.header.highlightArea);
                    header.surface = (header.x2 - header.x1) * (header.y2 - header.y1);
                    this._itemAreas.push(header);
                }
            }
        },
		/**
		 * Takes a contentItem or a configuration and optionally a parent
		 * item and returns an initialised instance of the contentItem.
		 * If the contentItem is a function, it is first called
		 *
		 * @packagePrivate
		 *
		 * @param   {lm.items.AbtractContentItem|Object|Function} contentItemOrConfig
		 * @param   {lm.items.AbtractContentItem} parent Only necessary when passing in config
		 *
		 * @returns {lm.items.AbtractContentItem}
		 */
        _$normalizeContentItem: function (contentItemOrConfig, parent) {
            if (!contentItemOrConfig) {
                throw new Error('No content item defined');
            }
            if (lm.utils.isFunction(contentItemOrConfig)) {
                contentItemOrConfig = contentItemOrConfig();
            }
            if (contentItemOrConfig instanceof lm.items.AbstractContentItem) {
                return contentItemOrConfig;
            }
            if ($.isPlainObject(contentItemOrConfig) && contentItemOrConfig.type) {
                var newContentItem = this.createContentItem(contentItemOrConfig, parent);
                newContentItem.callDownwards('_$init');
                return newContentItem;
            } else {
                throw new Error('Invalid contentItem');
            }
        },
		/**
		 * Iterates through the array of open popout windows and removes the ones
		 * that are effectively closed. This is necessary due to the lack of reliably
		 * listening for window.close / unload events in a cross browser compatible fashion.
		 *
		 * @packagePrivate
		 *
		 * @returns {void}
		 */
        _$reconcilePopoutWindows: function () {
            var openPopouts = [], i;
            for (i = 0; i < this.openPopouts.length; i++) {
                if (this.openPopouts[i].getWindow().closed === false) {
                    openPopouts.push(this.openPopouts[i]);
                } else {
                    this.emit('windowClosed', this.openPopouts[i]);
                }
            }
            if (this.openPopouts.length !== openPopouts.length) {
                this.emit('stateChanged');
                this.openPopouts = openPopouts;
            }
        },
		/***************************
		 * PRIVATE
		 ***************************/
		/**
		 * Returns a flattened array of all content items,
		 * regardles of level or type
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _getAllContentItems: function () {
            var allContentItems = [];
            var addChildren = function (contentItem) {
                allContentItems.push(contentItem);
                if (contentItem.contentItems instanceof Array) {
                    for (var i = 0; i < contentItem.contentItems.length; i++) {
                        addChildren(contentItem.contentItems[i]);
                    }
                }
            };
            addChildren(this.root);
            return allContentItems;
        },
		/**
		 * Binds to DOM/BOM events on init
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _bindEvents: function () {
            if (this._isFullPage) {
                $(window).resize(this._resizeFunction);
            }
            $(window).on('unload beforeunload', this._unloadFunction);
        },
		/**
		 * Debounces resize events
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _onResize: function () {
            clearTimeout(this._resizeTimeoutId);
            this._resizeTimeoutId = setTimeout(lm.utils.fnBind(this.updateSize, this), 100);
        },
		/**
		 * Extends the default config with the user specific settings and applies
		 * derivations. Please note that there's a seperate method (AbstractContentItem._extendItemNode)
		 * that deals with the extension of item configs
		 *
		 * @param   {Object} config
		 * @static
		 * @returns {Object} config
		 */
        _createConfig: function (config) {
            var windowConfigKey = lm.utils.getQueryStringParam('gl-window');
            if (windowConfigKey) {
                this.isSubWindow = true;
                config = localStorage.getItem(windowConfigKey);
                config = JSON.parse(config);
                config = (new lm.utils.ConfigMinifier()).unminifyConfig(config);
                localStorage.removeItem(windowConfigKey);
            }
            config = $.extend(true, {}, lm.config.defaultConfig, config);
            var nextNode = function (node) {
                for (var key in node) {
                    if (key !== 'props' && typeof node[key] === 'object') {
                        nextNode(node[key]);
                    }
                    else if (key === 'type' && node[key] === 'react-component') {
                        node.type = 'component';
                        node.componentName = 'lm-react-component';
                    }
                }
            }
            nextNode(config);
            if (config.settings.hasHeaders === false) {
                config.dimensions.headerHeight = 0;
            }
            return config;
        },
		/**
		 * This is executed when GoldenLayout detects that it is run
		 * within a previously opened popout window.
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _adjustToWindowMode: function () {
            var popInButton = $('');
            popInButton.click(lm.utils.fnBind(function () {
                this.emit('popIn');
            }, this));
            document.title = lm.utils.stripTags(this.config.content[0].title);
            $('head').append($('body link, body style, template, .gl_keep'));
            this.container = $('body')
                .html('')
                .css('visibility', 'visible')
                .append(popInButton);
			/*
			 * This seems a bit pointless, but actually causes a reflow/re-evaluation getting around
			 * slickgrid's "Cannot find stylesheet." bug in chrome
			 */
            var x = document.body.offsetHeight; // jshint ignore:line
			/*
			 * Expose this instance on the window object
			 * to allow the opening window to interact with
			 * it
			 */
            window.__glInstance = this;
        },
		/**
		 * Creates Subwindows (if there are any). Throws an error
		 * if popouts are blocked.
		 *
		 * @returns {void}
		 */
        _createSubWindows: function () {
            var i, popout;
            for (i = 0; i < this.config.openPopouts.length; i++) {
                popout = this.config.openPopouts[i];
                this.createPopout(
                    popout.content,
                    popout.dimensions,
                    popout.parentId,
                    popout.indexInParent
                );
            }
        },
		/**
		 * Determines what element the layout will be created in
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _setContainer: function () {
            var container = $(this.container || 'body');
            if (container.length === 0) {
                throw new Error('GoldenLayout container not found');
            }
            if (container.length > 1) {
                throw new Error('GoldenLayout more than one container element specified');
            }
            if (container[0] === document.body) {
                this._isFullPage = true;
                $('html, body').css({
                    height: '100%',
                    margin: 0,
                    padding: 0,
                    overflow: 'hidden'
                });
            }
            this.container = container;
        },
		/**
		 * Kicks of the initial, recursive creation chain
		 *
		 * @param   {Object} config GoldenLayout Config
		 *
		 * @returns {void}
		 */
        _create: function (config) {
            var errorMsg;
            if (!(config.content instanceof Array)) {
                if (config.content === undefined) {
                    errorMsg = 'Missing setting \'content\' on top level of configuration';
                } else {
                    errorMsg = 'Configuration parameter \'content\' must be an array';
                }
                throw new lm.errors.ConfigurationError(errorMsg, config);
            }
            if (config.content.length > 1) {
                errorMsg = 'Top level content can\'t contain more then one element.';
                throw new lm.errors.ConfigurationError(errorMsg, config);
            }
            this.root = new lm.items.Root(this, { content: config.content }, this.container);
            this.root.callDownwards('_$init');
            if (config.maximisedItemId === '__glMaximised') {
                this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise();
            }
        },
		/**
		 * Called when the window is closed or the user navigates away
		 * from the page
		 *
		 * @returns {void}
		 */
        _onUnload: function () {
            if (this.config.settings.closePopoutsOnUnload === true) {
                for (var i = 0; i < this.openPopouts.length; i++) {
                    this.openPopouts[i].close();
                }
            }
        },
		/**
		 * Adjusts the number of columns to be lower to fit the screen and still maintain minItemWidth.
		 *
		 * @returns {void}
		 */
        _adjustColumnsResponsive: function () {
            // If there is no min width set, or not content items, do nothing.
            if (!this._useResponsiveLayout() || this._updatingColumnsResponsive || !this.config.dimensions || !this.config.dimensions.minItemWidth || this.root.contentItems.length === 0 || !this.root.contentItems[0].isRow) {
                this._firstLoad = false;
                return;
            }
            this._firstLoad = false;
            // If there is only one column, do nothing.
            var columnCount = this.root.contentItems[0].contentItems.length;
            if (columnCount <= 1) {
                return;
            }
            // If they all still fit, do nothing.
            var minItemWidth = this.config.dimensions.minItemWidth;
            var totalMinWidth = columnCount * minItemWidth;
            if (totalMinWidth <= this.width) {
                return;
            }
            // Prevent updates while it is already happening.
            this._updatingColumnsResponsive = true;
            // Figure out how many columns to stack, and put them all in the first stack container.
            var finalColumnCount = Math.max(Math.floor(this.width / minItemWidth), 1);
            var stackColumnCount = columnCount - finalColumnCount;
            var rootContentItem = this.root.contentItems[0];
            var firstStackContainer = this._findAllStackContainers()[0];
            for (var i = 0; i < stackColumnCount; i++) {
                // Stack from right.
                var column = rootContentItem.contentItems[rootContentItem.contentItems.length - 1];
                this._addChildContentItemsToContainer(firstStackContainer, column);
            }
            this._updatingColumnsResponsive = false;
        },
		/**
		 * Determines if responsive layout should be used.
		 *
		 * @returns {bool} - True if responsive layout should be used; otherwise false.
		 */
        _useResponsiveLayout: function () {
            return this.config.settings && (this.config.settings.responsiveMode == 'always' || (this.config.settings.responsiveMode == 'onload' && this._firstLoad));
        },
		/**
		 * Adds all children of a node to another container recursively.
		 * @param {object} container - Container to add child content items to.
		 * @param {object} node - Node to search for content items.
		 * @returns {void}
		 */
        _addChildContentItemsToContainer: function (container, node) {
            if (node.type === 'stack') {
                node.contentItems.forEach(function (item) {
                    container.addChild(item);
                    node.removeChild(item, true);
                });
            }
            else {
                node.contentItems.forEach(lm.utils.fnBind(function (item) {
                    this._addChildContentItemsToContainer(container, item);
                }, this));
            }
        },
		/**
		 * Finds all the stack containers.
		 * @returns {array} - The found stack containers.
		 */
        _findAllStackContainers: function () {
            var stackContainers = [];
            this._findAllStackContainersRecursive(stackContainers, this.root);
            return stackContainers;
        },
		/**
		 * Finds all the stack containers.
		 *
		 * @param {array} - Set of containers to populate.
		 * @param {object} - Current node to process.
		 *
		 * @returns {void}
		 */
        _findAllStackContainersRecursive: function (stackContainers, node) {
            node.contentItems.forEach(lm.utils.fnBind(function (item) {
                if (item.type == 'stack') {
                    stackContainers.push(item);
                }
                else if (!item.isComponent) {
                    this._findAllStackContainersRecursive(stackContainers, item);
                }
            }, this));
        }
    });
	/**
	 * Expose the Layoutmanager as the single entrypoint using UMD
	 */
    (function () {
        /* global define */
        if (typeof define === 'function' && define.amd) {
            define(['jquery'], function (jquery) {
                $ = jquery;
                return lm.LayoutManager;
            }); // jshint ignore:line
        } else if (typeof exports === 'object') {
            module.exports = lm.LayoutManager;
        } else {
            window.GoldenLayout = lm.LayoutManager;
        }
    })();
    lm.config.itemDefaultConfig = {
        isClosable: true,
        reorderEnabled: true,
        title: ''
    };
    lm.config.defaultConfig = {
        openPopouts: [],
        settings: {
            hasHeaders: true,
            constrainDragToContainer: true,
            reorderEnabled: true,
            selectionEnabled: false,
            popoutWholeStack: false,
            blockedPopoutsThrowError: true,
            closePopoutsOnUnload: true,
            showPopoutIcon: true,
            showMaximiseIcon: true,
            showCloseIcon: true,
            responsiveMode: 'onload', // Can be onload, always, or none.
            tabOverlapAllowance: 0, // maximum pixel overlap per tab
            reorderOnTabMenuClick: true,
            tabControlOffset: 10
        },
        dimensions: {
            borderWidth: 5,
            borderGrabWidth: 15,
            minItemHeight: 10,
            minItemWidth: 10,
            headerHeight: 20,
            dragProxyWidth: 300,
            dragProxyHeight: 200
        },
        labels: {
            close: 'close',
            maximise: 'maximise',
            minimise: 'minimise',
            popout: 'open in new window',
            popin: 'pop in',
            tabDropdown: 'additional tabs'
        }
    };
    lm.container.ItemContainer = function (config, parent, layoutManager) {
        lm.utils.EventEmitter.call(this);
        this.width = null;
        this.height = null;
        this.title = config.componentName;
        this.parent = parent;
        this.layoutManager = layoutManager;
        this.isHidden = false;
        this._config = config;
        this._element = $([
            ''
        ].join(''));
        this._contentElement = this._element.find('.lm_content');
    };
    lm.utils.copy(lm.container.ItemContainer.prototype, {
		/**
		 * Get the inner DOM element the container's content
		 * is intended to live in
		 *
		 * @returns {DOM element}
		 */
        getElement: function () {
            return this._contentElement;
        },
		/**
		 * Hide the container. Notifies the containers content first
		 * and then hides the DOM node. If the container is already hidden
		 * this should have no effect
		 *
		 * @returns {void}
		 */
        hide: function () {
            this.emit('hide');
            this.isHidden = true;
            this._element.hide();
        },
		/**
		 * Shows a previously hidden container. Notifies the
		 * containers content first and then shows the DOM element.
		 * If the container is already visible this has no effect.
		 *
		 * @returns {void}
		 */
        show: function () {
            this.emit('show');
            this.isHidden = false;
            this._element.show();
            // call shown only if the container has a valid size
            if (this.height != 0 || this.width != 0) {
                this.emit('shown');
            }
        },
		/**
		 * Set the size from within the container. Traverses up
		 * the item tree until it finds a row or column element
		 * and resizes its items accordingly.
		 *
		 * If this container isn't a descendant of a row or column
		 * it returns false
		 * @todo  Rework!!!
		 * @param {Number} width  The new width in pixel
		 * @param {Number} height The new height in pixel
		 *
		 * @returns {Boolean} resizeSuccesful
		 */
        setSize: function (width, height) {
            var rowOrColumn = this.parent,
                rowOrColumnChild = this,
                totalPixel,
                percentage,
                direction,
                newSize,
                delta,
                i;
            while (!rowOrColumn.isColumn && !rowOrColumn.isRow) {
                rowOrColumnChild = rowOrColumn;
                rowOrColumn = rowOrColumn.parent;
				/**
				 * No row or column has been found
				 */
                if (rowOrColumn.isRoot) {
                    return false;
                }
            }
            direction = rowOrColumn.isColumn ? "height" : "width";
            newSize = direction === "height" ? height : width;
            totalPixel = this[direction] * (1 / (rowOrColumnChild.config[direction] / 100));
            percentage = (newSize / totalPixel) * 100;
            delta = (rowOrColumnChild.config[direction] - percentage) / (rowOrColumn.contentItems.length - 1);
            for (i = 0; i < rowOrColumn.contentItems.length; i++) {
                if (rowOrColumn.contentItems[i] === rowOrColumnChild) {
                    rowOrColumn.contentItems[i].config[direction] = percentage;
                } else {
                    rowOrColumn.contentItems[i].config[direction] += delta;
                }
            }
            rowOrColumn.callDownwards('setSize');
            return true;
        },
		/**
		 * Closes the container if it is closable. Can be called by
		 * both the component within at as well as the contentItem containing
		 * it. Emits a close event before the container itself is closed.
		 *
		 * @returns {void}
		 */
        close: function () {
            if (this._config.isClosable) {
                this.emit('close');
                this.parent.close();
            }
        },
		/**
		 * Returns the current state object
		 *
		 * @returns {Object} state
		 */
        getState: function () {
            return this._config.componentState;
        },
		/**
		 * Merges the provided state into the current one
		 *
		 * @param   {Object} state
		 *
		 * @returns {void}
		 */
        extendState: function (state) {
            this.setState($.extend(true, this.getState(), state));
        },
		/**
		 * Notifies the layout manager of a stateupdate
		 *
		 * @param {serialisable} state
		 */
        setState: function (state) {
            this._config.componentState = state;
            this.parent.emitBubblingEvent('stateChanged');
        },
		/**
		 * Set's the components title
		 *
		 * @param {String} title
		 */
        setTitle: function (title) {
            this.parent.setTitle(title);
        },
		/**
		 * Set's the containers size. Called by the container's component.
		 * To set the size programmatically from within the container please
		 * use the public setSize method
		 *
		 * @param {[Int]} width  in px
		 * @param {[Int]} height in px
		 *
		 * @returns {void}
		 */
        _$setSize: function (width, height) {
            if (width !== this.width || height !== this.height) {
                this.width = width;
                this.height = height;
                var cl = this._contentElement[0];
                var hdelta = cl.offsetWidth - cl.clientWidth;
                var vdelta = cl.offsetHeight - cl.clientHeight;
                this._contentElement.width(this.width - hdelta)
                    .height(this.height - vdelta);
                this.emit('resize');
            }
        }
    });
	/**
	 * Pops a content item out into a new browser window.
	 * This is achieved by
	 *
	 *    - Creating a new configuration with the content item as root element
	 *    - Serializing and minifying the configuration
	 *    - Opening the current window's URL with the configuration as a GET parameter
	 *    - GoldenLayout when opened in the new window will look for the GET parameter
	 *      and use it instead of the provided configuration
	 *
	 * @param {Object} config GoldenLayout item config
	 * @param {Object} dimensions A map with width, height, top and left
	 * @param {String} parentId The id of the element the item will be appended to on popIn
	 * @param {Number} indexInParent The position of this element within its parent
	 * @param {lm.LayoutManager} layoutManager
	 */
    lm.controls.BrowserPopout = function (config, dimensions, parentId, indexInParent, layoutManager) {
        lm.utils.EventEmitter.call(this);
        this.isInitialised = false;
        this._config = config;
        this._dimensions = dimensions;
        this._parentId = parentId;
        this._indexInParent = indexInParent;
        this._layoutManager = layoutManager;
        this._popoutWindow = null;
        this._id = null;
        this._createWindow();
    };
    lm.utils.copy(lm.controls.BrowserPopout.prototype, {
        toConfig: function () {
            if (this.isInitialised === false) {
                throw new Error('Can\'t create config, layout not yet initialised');
                return;
            }
            return {
                dimensions: {
                    width: this.getGlInstance().width,
                    height: this.getGlInstance().height,
                    left: this._popoutWindow.screenX || this._popoutWindow.screenLeft,
                    top: this._popoutWindow.screenY || this._popoutWindow.screenTop
                },
                content: this.getGlInstance().toConfig().content,
                parentId: this._parentId,
                indexInParent: this._indexInParent
            };
        },
        getGlInstance: function () {
            return this._popoutWindow.__glInstance;
        },
        getWindow: function () {
            return this._popoutWindow;
        },
        close: function () {
            if (this.getGlInstance()) {
                this.getGlInstance()._$closeWindow();
            } else {
                try {
                    this.getWindow().close();
                } catch (e) {
                }
            }
        },
		/**
		 * Returns the popped out item to its original position. If the original
		 * parent isn't available anymore it falls back to the layout's topmost element
		 */
        popIn: function () {
            var childConfig,
                parentItem,
                index = this._indexInParent;
            if (this._parentId) {
				/*
				 * The $.extend call seems a bit pointless, but it's crucial to
				 * copy the config returned by this.getGlInstance().toConfig()
				 * onto a new object. Internet Explorer keeps the references
				 * to objects on the child window, resulting in the following error
				 * once the child window is closed:
				 *
				 * The callee (server [not server application]) is not available and disappeared
				 */
                childConfig = $.extend(true, {}, this.getGlInstance().toConfig()).content[0];
                parentItem = this._layoutManager.root.getItemsById(this._parentId)[0];
				/*
				 * Fallback if parentItem is not available. Either add it to the topmost
				 * item or make it the topmost item if the layout is empty
				 */
                if (!parentItem) {
                    if (this._layoutManager.root.contentItems.length > 0) {
                        parentItem = this._layoutManager.root.contentItems[0];
                    } else {
                        parentItem = this._layoutManager.root;
                    }
                    index = 0;
                }
            }
            parentItem.addChild(childConfig, this._indexInParent);
            this.close();
        },
		/**
		 * Creates the URL and window parameter
		 * and opens a new window
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _createWindow: function () {
            var checkReadyInterval,
                url = this._createUrl(),
				/**
				 * Bogus title to prevent re-usage of existing window with the
				 * same title. The actual title will be set by the new window's
				 * GoldenLayout instance if it detects that it is in subWindowMode
				 */
                title = Math.floor(Math.random() * 1000000).toString(36),
				/**
				 * The options as used in the window.open string
				 */
                options = this._serializeWindowOptions({
                    width: this._dimensions.width,
                    height: this._dimensions.height,
                    innerWidth: this._dimensions.width,
                    innerHeight: this._dimensions.height,
                    menubar: 'no',
                    toolbar: 'no',
                    location: 'no',
                    personalbar: 'no',
                    resizable: 'yes',
                    scrollbars: 'no',
                    status: 'no'
                });
            this._popoutWindow = window.open(url, title, options);
            if (!this._popoutWindow) {
                if (this._layoutManager.config.settings.blockedPopoutsThrowError === true) {
                    var error = new Error('Popout blocked');
                    error.type = 'popoutBlocked';
                    throw error;
                } else {
                    return;
                }
            }
            $(this._popoutWindow)
                .on('load', lm.utils.fnBind(this._positionWindow, this))
                .on('unload beforeunload', lm.utils.fnBind(this._onClose, this));
			/**
			 * Polling the childwindow to find out if GoldenLayout has been initialised
			 * doesn't seem optimal, but the alternatives - adding a callback to the parent
			 * window or raising an event on the window object - both would introduce knowledge
			 * about the parent to the child window which we'd rather avoid
			 */
            checkReadyInterval = setInterval(lm.utils.fnBind(function () {
                if (this._popoutWindow.__glInstance && this._popoutWindow.__glInstance.isInitialised) {
                    this._onInitialised();
                    clearInterval(checkReadyInterval);
                }
            }, this), 10);
        },
		/**
		 * Serialises a map of key:values to a window options string
		 *
		 * @param   {Object} windowOptions
		 *
		 * @returns {String} serialised window options
		 */
        _serializeWindowOptions: function (windowOptions) {
            var windowOptionsString = [], key;
            for (key in windowOptions) {
                windowOptionsString.push(key + '=' + windowOptions[key]);
            }
            return windowOptionsString.join(',');
        },
		/**
		 * Creates the URL for the new window, including the
		 * config GET parameter
		 *
		 * @returns {String} URL
		 */
        _createUrl: function () {
            var config = { content: this._config },
                storageKey = 'gl-window-config-' + lm.utils.getUniqueId(),
                urlParts;
            config = (new lm.utils.ConfigMinifier()).minifyConfig(config);
            try {
                localStorage.setItem(storageKey, JSON.stringify(config));
            } catch (e) {
                throw new Error('Error while writing to localStorage ' + e.toString());
            }
            urlParts = document.location.href.split('?');
            // URL doesn't contain GET-parameters
            if (urlParts.length === 1) {
                return urlParts[0] + '?gl-window=' + storageKey;
                // URL contains GET-parameters
            } else {
                return document.location.href + '&gl-window=' + storageKey;
            }
        },
		/**
		 * Move the newly created window roughly to
		 * where the component used to be.
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _positionWindow: function () {
            this._popoutWindow.moveTo(this._dimensions.left, this._dimensions.top);
            this._popoutWindow.focus();
        },
		/**
		 * Callback when the new window is opened and the GoldenLayout instance
		 * within it is initialised
		 *
		 * @returns {void}
		 */
        _onInitialised: function () {
            this.isInitialised = true;
            this.getGlInstance().on('popIn', this.popIn, this);
            this.emit('initialised');
        },
		/**
		 * Invoked 50ms after the window unload event
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _onClose: function () {
            setTimeout(lm.utils.fnBind(this.emit, this, ['closed']), 50);
        }
    });
	/**
	 * This class creates a temporary container
	 * for the component whilst it is being dragged
	 * and handles drag events
	 *
	 * @constructor
	 * @private
	 *
	 * @param {Number} x              The initial x position
	 * @param {Number} y              The initial y position
	 * @param {lm.utils.DragListener} dragListener
	 * @param {lm.LayoutManager} layoutManager
	 * @param {lm.item.AbstractContentItem} contentItem
	 * @param {lm.item.AbstractContentItem} originalParent
	 */
    lm.controls.DragProxy = function (x, y, dragListener, layoutManager, contentItem, originalParent) {
        lm.utils.EventEmitter.call(this);
        this._dragListener = dragListener;
        this._layoutManager = layoutManager;
        this._contentItem = contentItem;
        this._originalParent = originalParent;
        this._area = null;
        this._lastValidArea = null;
        this._dragListener.on('drag', this._onDrag, this);
        this._dragListener.on('dragStop', this._onDrop, this);
        this.element = $(lm.controls.DragProxy._template);
        if (originalParent && originalParent._side) {
            this._sided = originalParent._sided;
            this.element.addClass('lm_' + originalParent._side);
            if (['right', 'bottom'].indexOf(originalParent._side) >= 0)
                this.element.find('.lm_content').after(this.element.find('.lm_header'));
        }
        this.element.css({ left: x, top: y });
        this.element.find('.lm_tab').attr('title', lm.utils.stripTags(this._contentItem.config.title));
        this.element.find('.lm_title').html(this._contentItem.config.title);
        this.childElementContainer = this.element.find('.lm_content');
        this.childElementContainer.append(contentItem.element);
        this._updateTree();
        this._layoutManager._$calculateItemAreas();
        this._setDimensions();
        $(document.body).append(this.element);
        var offset = this._layoutManager.container.offset();
        this._minX = offset.left;
        this._minY = offset.top;
        this._maxX = this._layoutManager.container.width() + this._minX;
        this._maxY = this._layoutManager.container.height() + this._minY;
        this._width = this.element.width();
        this._height = this.element.height();
        this._setDropPosition(x, y);
    };
    lm.controls.DragProxy._template = '';
    lm.utils.copy(lm.controls.DragProxy.prototype, {
		/**
		 * Callback on every mouseMove event during a drag. Determines if the drag is
		 * still within the valid drag area and calls the layoutManager to highlight the
		 * current drop area
		 *
		 * @param   {Number} offsetX The difference from the original x position in px
		 * @param   {Number} offsetY The difference from the original y position in px
		 * @param   {jQuery DOM event} event
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _onDrag: function (offsetX, offsetY, event) {
            event = event.originalEvent && event.originalEvent.touches ? event.originalEvent.touches[0] : event;
            var x = event.pageX,
                y = event.pageY,
                isWithinContainer = x > this._minX && x < this._maxX && y > this._minY && y < this._maxY;
            if (!isWithinContainer && this._layoutManager.config.settings.constrainDragToContainer === true) {
                return;
            }
            this._setDropPosition(x, y);
        },
		/**
		 * Sets the target position, highlighting the appropriate area
		 *
		 * @param   {Number} x The x position in px
		 * @param   {Number} y The y position in px
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _setDropPosition: function (x, y) {
            this.element.css({ left: x, top: y });
            this._area = this._layoutManager._$getArea(x, y);
            if (this._area !== null) {
                this._lastValidArea = this._area;
                this._area.contentItem._$highlightDropZone(x, y, this._area);
            }
        },
		/**
		 * Callback when the drag has finished. Determines the drop area
		 * and adds the child to it
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _onDrop: function () {
            this._layoutManager.dropTargetIndicator.hide();
			/*
			 * Valid drop area found
			 */
            if (this._area !== null) {
                this._area.contentItem._$onDrop(this._contentItem, this._area);
				/**
				 * No valid drop area available at present, but one has been found before.
				 * Use it
				 */
            } else if (this._lastValidArea !== null) {
                this._lastValidArea.contentItem._$onDrop(this._contentItem, this._lastValidArea);
				/**
				 * No valid drop area found during the duration of the drag. Return
				 * content item to its original position if a original parent is provided.
				 * (Which is not the case if the drag had been initiated by createDragSource)
				 */
            } else if (this._originalParent) {
                this._originalParent.addChild(this._contentItem);
				/**
				 * The drag didn't ultimately end up with adding the content item to
				 * any container. In order to ensure clean up happens, destroy the
				 * content item.
				 */
            } else {
                this._contentItem._$destroy();
            }
            this.element.remove();
            this._layoutManager.emit('itemDropped', this._contentItem);
        },
		/**
		 * Removes the item from its original position within the tree
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _updateTree: function () {
			/**
			 * parent is null if the drag had been initiated by a external drag source
			 */
            if (this._contentItem.parent) {
                this._contentItem.parent.removeChild(this._contentItem, true);
            }
            this._contentItem._$setParent(this);
        },
		/**
		 * Updates the Drag Proxie's dimensions
		 *
		 * @private
		 *
		 * @returns {void}
		 */
        _setDimensions: function () {
            var dimensions = this._layoutManager.config.dimensions,
                width = dimensions.dragProxyWidth,
                height = dimensions.dragProxyHeight;
            this.element.width(width);
            this.element.height(height);
            width -= (this._sided ? dimensions.headerHeight : 0);
            height -= (!this._sided ? dimensions.headerHeight : 0);
            this.childElementContainer.width(width);
            this.childElementContainer.height(height);
            this._contentItem.element.width(width);
            this._contentItem.element.height(height);
            this._contentItem.callDownwards('_$show');
            this._contentItem.callDownwards('setSize');
        }
    });
	/**
	 * Allows for any DOM item to create a component on drag
	 * start tobe dragged into the Layout
	 *
	 * @param {jQuery element} element
	 * @param {Object} itemConfig the configuration for the contentItem that will be created
	 * @param {LayoutManager} layoutManager
	 *
	 * @constructor
	 */
    lm.controls.DragSource = function (element, itemConfig, layoutManager) {
        this._element = element;
        this._itemConfig = itemConfig;
        this._layoutManager = layoutManager;
        this._dragListener = null;
        this._createDragListener();
    };
    lm.utils.copy(lm.controls.DragSource.prototype, {
		/**
		 * Called initially and after every drag
		 *
		 * @returns {void}
		 */
        _createDragListener: function () {
            if (this._dragListener !== null) {
                this._dragListener.destroy();
            }
            this._dragListener = new lm.utils.DragListener(this._element);
            this._dragListener.on('dragStart', this._onDragStart, this);
            this._dragListener.on('dragStop', this._createDragListener, this);
        },
		/**
		 * Callback for the DragListener's dragStart event
		 *
		 * @param   {int} x the x position of the mouse on dragStart
		 * @param   {int} y the x position of the mouse on dragStart
		 *
		 * @returns {void}
		 */
        _onDragStart: function (x, y) {
            var itemConfig = this._itemConfig;
            if (lm.utils.isFunction(itemConfig)) {
                itemConfig = itemConfig();
            }
            var contentItem = this._layoutManager._$normalizeContentItem($.extend(true, {}, itemConfig)),
                dragProxy = new lm.controls.DragProxy(x, y, this._dragListener, this._layoutManager, contentItem, null);
            this._layoutManager.transitionIndicator.transitionElements(this._element, dragProxy.element);
        }
    });
    lm.controls.DropTargetIndicator = function () {
        this.element = $(lm.controls.DropTargetIndicator._template);
        $(document.body).append(this.element);
    };
    lm.controls.DropTargetIndicator._template = '';
    lm.utils.copy(lm.controls.DropTargetIndicator.prototype, {
        destroy: function () {
            this.element.remove();
        },
        highlight: function (x1, y1, x2, y2) {
            this.highlightArea({ x1: x1, y1: y1, x2: x2, y2: y2 });
        },
        highlightArea: function (area) {
            this.element.css({
                left: area.x1,
                top: area.y1,
                width: area.x2 - area.x1,
                height: area.y2 - area.y1
            }).show();
        },
        hide: function () {
            this.element.hide();
        }
    });
	/**
	 * This class represents a header above a Stack ContentItem.
	 *
	 * @param {lm.LayoutManager} layoutManager
	 * @param {lm.item.AbstractContentItem} parent
	 */
    lm.controls.Header = function (layoutManager, parent) {
        lm.utils.EventEmitter.call(this);
        this.layoutManager = layoutManager;
        this.element = $(lm.controls.Header._template);
        if (this.layoutManager.config.settings.selectionEnabled === true) {
            this.element.addClass('lm_selectable');
            this.element.on('click touchstart', lm.utils.fnBind(this._onHeaderClick, this));
        }
        this.tabsContainer = this.element.find('.lm_tabs');
        this.tabDropdownContainer = this.element.find('.lm_tabdropdown_list');
        this.tabDropdownContainer.hide();
        this.controlsContainer = this.element.find('.lm_controls');
        this.parent = parent;
        this.parent.on('resize', this._updateTabSizes, this);
        this.tabs = [];
        this.activeContentItem = null;
        this.closeButton = null;
        this.tabDropdownButton = null;
        this.hideAdditionalTabsDropdown = lm.utils.fnBind(this._hideAdditionalTabsDropdown, this);
        $(document).mouseup(this.hideAdditionalTabsDropdown);
        this._lastVisibleTabIndex = -1;
        this._tabControlOffset = this.layoutManager.config.settings.tabControlOffset;
        this._createControls();
    };
    lm.controls.Header._template = [
        ''
    ].join('');
    lm.utils.copy(lm.controls.Header.prototype, {
		/**
		 * Creates a new tab and associates it with a contentItem
		 *
		 * @param    {lm.item.AbstractContentItem} contentItem
		 * @param    {Integer} index The position of the tab
		 *
		 * @returns {void}
		 */
        createTab: function (contentItem, index) {
            var tab, i;
            //If there's already a tab relating to the
            //content item, don't do anything
            for (i = 0; i < this.tabs.length; i++) {
                if (this.tabs[i].contentItem === contentItem) {
                    return;
                }
            }
            tab = new lm.controls.Tab(this, contentItem);
            if (this.tabs.length === 0) {
                this.tabs.push(tab);
                this.tabsContainer.append(tab.element);
                return;
            }
            if (index === undefined) {
                index = this.tabs.length;
            }
            if (index > 0) {
                this.tabs[index - 1].element.after(tab.element);
            } else {
                this.tabs[0].element.before(tab.element);
            }
            this.tabs.splice(index, 0, tab);
            this._updateTabSizes();
        },
		/**
		 * Finds a tab based on the contentItem its associated with and removes it.
		 *
		 * @param    {lm.item.AbstractContentItem} contentItem
		 *
		 * @returns {void}
		 */
        removeTab: function (contentItem) {
            for (var i = 0; i < this.tabs.length; i++) {
                if (this.tabs[i].contentItem === contentItem) {
                    this.tabs[i]._$destroy();
                    this.tabs.splice(i, 1);
                    return;
                }
            }
            throw new Error('contentItem is not controlled by this header');
        },
		/**
		 * The programmatical equivalent of clicking a Tab.
		 *
		 * @param {lm.item.AbstractContentItem} contentItem
		 */
        setActiveContentItem: function (contentItem) {
            var i, j, isActive, activeTab;
            for (i = 0; i < this.tabs.length; i++) {
                isActive = this.tabs[i].contentItem === contentItem;
                this.tabs[i].setActive(isActive);
                if (isActive === true) {
                    this.activeContentItem = contentItem;
                    this.parent.config.activeItemIndex = i;
                }
            }
            if (this.layoutManager.config.settings.reorderOnTabMenuClick) {
				/**
				 * If the tab selected was in the dropdown, move everything down one to make way for this one to be the first.
				 * This will make sure the most used tabs stay visible.
				 */
                if (this._lastVisibleTabIndex !== -1 && this.parent.config.activeItemIndex > this._lastVisibleTabIndex) {
                    activeTab = this.tabs[this.parent.config.activeItemIndex];
                    for (j = this.parent.config.activeItemIndex; j > 0; j--) {
                        this.tabs[j] = this.tabs[j - 1];
                    }
                    this.tabs[0] = activeTab;
                    this.parent.config.activeItemIndex = 0;
                }
            }
            this._updateTabSizes();
            this.parent.emitBubblingEvent('stateChanged');
        },
		/**
		 * Programmatically operate with header position.
		 *
		 * @param {string} position one of ('top','left','right','bottom') to set or empty to get it.
		 *
		 * @returns {string} previous header position
		 */
        position: function (position) {
            var previous = this.parent._header.show;
            if (previous && !this.parent._side)
                previous = 'top';
            if (position !== undefined && this.parent._header.show != position) {
                this.parent._header.show = position;
                this.parent._setupHeaderPosition();
            }
            return previous;
        },
		/**
		 * Programmatically set closability.
		 *
		 * @package private
		 * @param {Boolean} isClosable Whether to enable/disable closability.
		 *
		 * @returns {Boolean} Whether the action was successful
		 */
        _$setClosable: function (isClosable) {
            if (this.closeButton && this._isClosable()) {
                this.closeButton.element[isClosable ? "show" : "hide"]();
                return true;
            }
            return false;
        },
		/**
		 * Destroys the entire header
		 *
		 * @package private
		 *
		 * @returns {void}
		 */
        _$destroy: function () {
            this.emit('destroy', this);
            for (var i = 0; i < this.tabs.length; i++) {
                this.tabs[i]._$destroy();
            }
            $(document).off('mouseup', this.hideAdditionalTabsDropdown);
            this.element.remove();
        },
		/**
		 * get settings from header
		 *
		 * @returns {string} when exists
		 */
        _getHeaderSetting: function (name) {
            if (name in this.parent._header)
                return this.parent._header[name];
        },
		/**
		 * Creates the popout, maximise and close buttons in the header's top right corner
		 *
		 * @returns {void}
		 */
        _createControls: function () {
            var closeStack,
                popout,
                label,
                maximiseLabel,
                minimiseLabel,
                maximise,
                maximiseButton,
                tabDropdownLabel,
                showTabDropdown;
			/**
			 * Dropdown to show additional tabs.
			 */
            showTabDropdown = lm.utils.fnBind(this._showAdditionalTabsDropdown, this);
            tabDropdownLabel = this.layoutManager.config.labels.tabDropdown;
            this.tabDropdownButton = new lm.controls.HeaderButton(this, tabDropdownLabel, 'lm_tabdropdown', showTabDropdown);
            this.tabDropdownButton.element.hide();
			/**
			 * Popout control to launch component in new window.
			 */
            if (this._getHeaderSetting('popout')) {
                popout = lm.utils.fnBind(this._onPopoutClick, this);
                label = this._getHeaderSetting('popout');
                new lm.controls.HeaderButton(this, label, 'lm_popout', popout);
            }
			/**
			 * Maximise control - set the component to the full size of the layout
			 */
            if (this._getHeaderSetting('maximise')) {
                maximise = lm.utils.fnBind(this.parent.toggleMaximise, this.parent);
                maximiseLabel = this._getHeaderSetting('maximise');
                minimiseLabel = this._getHeaderSetting('minimise');
                maximiseButton = new lm.controls.HeaderButton(this, maximiseLabel, 'lm_maximise', maximise);
                this.parent.on('maximised', function () {
                    maximiseButton.element.attr('title', minimiseLabel);
                });
                this.parent.on('minimised', function () {
                    maximiseButton.element.attr('title', maximiseLabel);
                });
            }
			/**
			 * Close button
			 */
            if (this._isClosable()) {
                closeStack = lm.utils.fnBind(this.parent.remove, this.parent);
                label = this._getHeaderSetting('close');
                this.closeButton = new lm.controls.HeaderButton(this, label, 'lm_close', closeStack);
            }
        },
		/**
		 * Shows drop down for additional tabs when there are too many to display.
		 *
		 * @returns {void}
		 */
        _showAdditionalTabsDropdown: function () {
            this.tabDropdownContainer.show();
        },
		/**
		 * Hides drop down for additional tabs when there are too many to display.
		 *
		 * @returns {void}
		 */
        _hideAdditionalTabsDropdown: function (e) {
            this.tabDropdownContainer.hide();
        },
		/**
		 * Checks whether the header is closable based on the parent config and
		 * the global config.
		 *
		 * @returns {Boolean} Whether the header is closable.
		 */
        _isClosable: function () {
            return this.parent.config.isClosable && this.layoutManager.config.settings.showCloseIcon;
        },
        _onPopoutClick: function () {
            if (this.layoutManager.config.settings.popoutWholeStack === true) {
                this.parent.popout();
            } else {
                this.activeContentItem.popout();
            }
        },
		/**
		 * Invoked when the header's background is clicked (not it's tabs or controls)
		 *
		 * @param    {jQuery DOM event} event
		 *
		 * @returns {void}
		 */
        _onHeaderClick: function (event) {
            if (event.target === this.element[0]) {
                this.parent.select();
            }
        },
		/**
		 * Pushes the tabs to the tab dropdown if the available space is not sufficient
		 *
		 * @returns {void}
		 */
        _updateTabSizes: function (showTabMenu) {
            if (this.tabs.length === 0) {
                return;
            }
            //Show the menu based on function argument
            this.tabDropdownButton.element.toggle(showTabMenu === true);
            var size = function (val) {
                return val ? 'width' : 'height';
            };
            this.element.css(size(!this.parent._sided), '');
            this.element[size(this.parent._sided)](this.layoutManager.config.dimensions.headerHeight);
            var availableWidth = this.element.outerWidth() - this.controlsContainer.outerWidth() - this._tabControlOffset,
                cumulativeTabWidth = 0,
                visibleTabWidth = 0,
                tabElement,
                i,
                j,
                marginLeft,
                overlap = 0,
                tabWidth,
                tabOverlapAllowance = this.layoutManager.config.settings.tabOverlapAllowance,
                tabOverlapAllowanceExceeded = false,
                activeIndex = (this.activeContentItem ? this.tabs.indexOf(this.activeContentItem.tab) : 0),
                activeTab = this.tabs[activeIndex];
            if (this.parent._sided)
                availableWidth = this.element.outerHeight() - this.controlsContainer.outerHeight() - this._tabControlOffset;
            this._lastVisibleTabIndex = -1;
            for (i = 0; i < this.tabs.length; i++) {
                tabElement = this.tabs[i].element;
                //Put the tab in the tabContainer so its true width can be checked
                this.tabsContainer.append(tabElement);
                tabWidth = tabElement.outerWidth() + parseInt(tabElement.css('margin-right'), 10);
                cumulativeTabWidth += tabWidth;
                //Include the active tab's width if it isn't already
                //This is to ensure there is room to show the active tab
                if (activeIndex <= i) {
                    visibleTabWidth = cumulativeTabWidth;
                } else {
                    visibleTabWidth = cumulativeTabWidth + activeTab.element.outerWidth() + parseInt(activeTab.element.css('margin-right'), 10);
                }
                // If the tabs won't fit, check the overlap allowance.
                if (visibleTabWidth > availableWidth) {
                    //Once allowance is exceeded, all remaining tabs go to menu.
                    if (!tabOverlapAllowanceExceeded) {
                        //No overlap for first tab or active tab
                        //Overlap spreads among non-active, non-first tabs
                        if (activeIndex > 0 && activeIndex <= i) {
                            overlap = (visibleTabWidth - availableWidth) / (i - 1);
                        } else {
                            overlap = (visibleTabWidth - availableWidth) / i;
                        }
                        //Check overlap against allowance.
                        if (overlap < tabOverlapAllowance) {
                            for (j = 0; j <= i; j++) {
                                marginLeft = (j !== activeIndex && j !== 0) ? '-' + overlap + 'px' : '';
                                this.tabs[j].element.css({ 'z-index': i - j, 'margin-left': marginLeft });
                            }
                            this._lastVisibleTabIndex = i;
                            this.tabsContainer.append(tabElement);
                        } else {
                            tabOverlapAllowanceExceeded = true;
                        }
                    } else if (i === activeIndex) {
                        //Active tab should show even if allowance exceeded. (We left room.)
                        tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
                        this.tabsContainer.append(tabElement);
                    }
                    if (tabOverlapAllowanceExceeded && i !== activeIndex) {
                        if (showTabMenu) {
                            //Tab menu already shown, so we just add to it.
                            tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
                            this.tabDropdownContainer.append(tabElement);
                        } else {
                            //We now know the tab menu must be shown, so we have to recalculate everything.
                            this._updateTabSizes(true);
                            return;
                        }
                    }
                }
                else {
                    this._lastVisibleTabIndex = i;
                    tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
                    this.tabsContainer.append(tabElement);
                }
            }
        }
    });
    lm.controls.HeaderButton = function (header, label, cssClass, action) {
        this._header = header;
        this.element = $('');
        this._header.on('destroy', this._$destroy, this);
        this._action = action;
        this.element.on('click touchstart', this._action);
        this._header.controlsContainer.append(this.element);
    };
    lm.utils.copy(lm.controls.HeaderButton.prototype, {
        _$destroy: function () {
            this.element.off();
            this.element.remove();
        }
    });
    lm.controls.Splitter = function (isVertical, size, grabSize) {
        this._isVertical = isVertical;
        this._size = size;
        this._grabSize = grabSize < size ? size : grabSize;
        this.element = this._createElement();
        this._dragListener = new lm.utils.DragListener(this.element);
    };
    lm.utils.copy(lm.controls.Splitter.prototype, {
        on: function (event, callback, context) {
            this._dragListener.on(event, callback, context);
        },
        _$destroy: function () {
            this.element.remove();
        },
        _createElement: function () {
            var dragHandle = $('');
            var element = $('');
            element.append(dragHandle);
            var handleExcessSize = this._grabSize - this._size;
            var handleExcessPos = handleExcessSize / 2;
            if (this._isVertical) {
                dragHandle.css('top', -handleExcessPos);
                dragHandle.css('height', this._size + handleExcessSize);
                element.addClass('lm_vertical');
                element['height'](this._size);
            } else {
                dragHandle.css('left', -handleExcessPos);
                dragHandle.css('width', this._size + handleExcessSize);
                element.addClass('lm_horizontal');
                element['width'](this._size);
            }
            return element;
        }
    });
	/**
	 * Represents an individual tab within a Stack's header
	 *
	 * @param {lm.controls.Header} header
	 * @param {lm.items.AbstractContentItem} contentItem
	 *
	 * @constructor
	 */
    lm.controls.Tab = function (header, contentItem) {
        this.header = header;
        this.contentItem = contentItem;
        this.element = $(lm.controls.Tab._template);
        this.titleElement = this.element.find('.lm_title');
        this.closeElement = this.element.find('.lm_close_tab');
        this.closeElement[contentItem.config.isClosable ? 'show' : 'hide']();
        this.isActive = false;
        this.setTitle(contentItem.config.title);
        this.contentItem.on('titleChanged', this.setTitle, this);
        this._layoutManager = this.contentItem.layoutManager;
        if (
            this._layoutManager.config.settings.reorderEnabled === true &&
            contentItem.config.reorderEnabled === true
        ) {
            this._dragListener = new lm.utils.DragListener(this.element);
            this._dragListener.on('dragStart', this._onDragStart, this);
            this.contentItem.on('destroy', this._dragListener.destroy, this._dragListener);
        }
        this._onTabClickFn = lm.utils.fnBind(this._onTabClick, this);
        this._onCloseClickFn = lm.utils.fnBind(this._onCloseClick, this);
        this.element.on('mousedown touchstart', this._onTabClickFn);
        if (this.contentItem.config.isClosable) {
            this.closeElement.on('click touchstart', this._onCloseClickFn);
            this.closeElement.on('mousedown', this._onCloseMousedown);
        } else {
            this.closeElement.remove();
        }
        this.contentItem.tab = this;
        this.contentItem.emit('tab', this);
        this.contentItem.layoutManager.emit('tabCreated', this);
        if (this.contentItem.isComponent) {
            this.contentItem.container.tab = this;
            this.contentItem.container.emit('tab', this);
        }
    };
	/**
	 * The tab's html template
	 *
	 * @type {String}
	 */
    lm.controls.Tab._template = '' +
        '' +
        '';
    lm.utils.copy(lm.controls.Tab.prototype, {
		/**
		 * Sets the tab's title to the provided string and sets
		 * its title attribute to a pure text representation (without
		 * html tags) of the same string.
		 *
		 * @public
		 * @param {String} title can contain html
		 */
        setTitle: function (title) {
            this.element.attr('title', lm.utils.stripTags(title));
            this.titleElement.html(title);
        },
		/**
		 * Sets this tab's active state. To programmatically
		 * switch tabs, use header.setActiveContentItem( item ) instead.
		 *
		 * @public
		 * @param {Boolean} isActive
		 */
        setActive: function (isActive) {
            if (isActive === this.isActive) {
                return;
            }
            this.isActive = isActive;
            if (isActive) {
                this.element.addClass('lm_active');
            } else {
                this.element.removeClass('lm_active');
            }
        },
		/**
		 * Destroys the tab
		 *
		 * @private
		 * @returns {void}
		 */
        _$destroy: function () {
            this._layoutManager.emit('tabDestroyed', this);
            this.element.off('mousedown touchstart', this._onTabClickFn);
            this.closeElement.off('click touchstart', this._onCloseClickFn);
            if (this._dragListener) {
                this.contentItem.off('destroy', this._dragListener.destroy, this._dragListener);
                this._dragListener.off('dragStart', this._onDragStart);
                this._dragListener = null;
            }
            this.element.remove();
        },
		/**
		 * Callback for the DragListener
		 *
		 * @param   {Number} x The tabs absolute x position
		 * @param   {Number} y The tabs absolute y position
		 *
		 * @private
		 * @returns {void}
		 */
        _onDragStart: function (x, y) {
            if (this.contentItem.parent.isMaximised === true) {
                this.contentItem.parent.toggleMaximise();
            }
            new lm.controls.DragProxy(
                x,
                y,
                this._dragListener,
                this._layoutManager,
                this.contentItem,
                this.header.parent
            );
        },
		/**
		 * Callback when the tab is clicked
		 *
		 * @param {jQuery DOM event} event
		 *
		 * @private
		 * @returns {void}
		 */
        _onTabClick: function (event) {
            // left mouse button or tap
            if (event.button === 0 || event.type === 'touchstart') {
                var activeContentItem = this.header.parent.getActiveContentItem();
                if (this.contentItem !== activeContentItem) {
                    this.header.parent.setActiveContentItem(this.contentItem);
                }
                // middle mouse button
            } else if (event.button === 1 && this.contentItem.config.isClosable) {
                this._onCloseClick(event);
            }
        },
		/**
		 * Callback when the tab's close button is
		 * clicked
		 *
		 * @param   {jQuery DOM event} event
		 *
		 * @private
		 * @returns {void}
		 */
        _onCloseClick: function (event) {
            event.stopPropagation();
            this.header.parent.removeChild(this.contentItem);
        },
		/**
		 * Callback to capture tab close button mousedown
		 * to prevent tab from activating.
		 *
		 * @param (jQuery DOM event) event
		 *
		 * @private
		 * @returns {void}
		 */
        _onCloseMousedown: function (event) {
            event.stopPropagation();
        }
    });
    lm.controls.TransitionIndicator = function () {
        this._element = $('');
        $(document.body).append(this._element);
        this._toElement = null;
        this._fromDimensions = null;
        this._totalAnimationDuration = 200;
        this._animationStartTime = null;
    };
    lm.utils.copy(lm.controls.TransitionIndicator.prototype, {
        destroy: function () {
            this._element.remove();
        },
        transitionElements: function (fromElement, toElement) {
			/**
			 * TODO - This is not quite as cool as expected. Review.
			 */
            return;
            this._toElement = toElement;
            this._animationStartTime = lm.utils.now();
            this._fromDimensions = this._measure(fromElement);
            this._fromDimensions.opacity = 0.8;
            this._element.show().css(this._fromDimensions);
            lm.utils.animFrame(lm.utils.fnBind(this._nextAnimationFrame, this));
        },
        _nextAnimationFrame: function () {
            var toDimensions = this._measure(this._toElement),
                animationProgress = (lm.utils.now() - this._animationStartTime) / this._totalAnimationDuration,
                currentFrameStyles = {},
                cssProperty;
            if (animationProgress >= 1) {
                this._element.hide();
                return;
            }
            toDimensions.opacity = 0;
            for (cssProperty in this._fromDimensions) {
                currentFrameStyles[cssProperty] = this._fromDimensions[cssProperty] +
                    (toDimensions[cssProperty] - this._fromDimensions[cssProperty]) *
                    animationProgress;
            }
            this._element.css(currentFrameStyles);
            lm.utils.animFrame(lm.utils.fnBind(this._nextAnimationFrame, this));
        },
        _measure: function (element) {
            var offset = element.offset();
            return {
                left: offset.left,
                top: offset.top,
                width: element.outerWidth(),
                height: element.outerHeight()
            };
        }
    });
    lm.errors.ConfigurationError = function (message, node) {
        Error.call(this);
        this.name = 'Configuration Error';
        this.message = message;
        this.node = node;
    };
    lm.errors.ConfigurationError.prototype = new Error();
	/**
	 * This is the baseclass that all content items inherit from.
	 * Most methods provide a subset of what the sub-classes do.
	 *
	 * It also provides a number of functions for tree traversal
	 *
	 * @param {lm.LayoutManager} layoutManager
	 * @param {item node configuration} config
	 * @param {lm.item} parent
	 *
	 * @event stateChanged
	 * @event beforeItemDestroyed
	 * @event itemDestroyed
	 * @event itemCreated
	 * @event componentCreated
	 * @event rowCreated
	 * @event columnCreated
	 * @event stackCreated
	 *
	 * @constructor
	 */
    lm.items.AbstractContentItem = function (layoutManager, config, parent) {
        lm.utils.EventEmitter.call(this);
        this.config = this._extendItemNode(config);
        this.type = config.type;
        this.contentItems = [];
        this.parent = parent;
        this.isInitialised = false;
        this.isMaximised = false;
        this.isRoot = false;
        this.isRow = false;
        this.isColumn = false;
        this.isStack = false;
        this.isComponent = false;
        this.layoutManager = layoutManager;
        this._pendingEventPropagations = {};
        this._throttledEvents = ['stateChanged'];
        this.on(lm.utils.EventEmitter.ALL_EVENT, this._propagateEvent, this);
        if (config.content) {
            this._createContentItems(config);
        }
    };
    lm.utils.copy(lm.items.AbstractContentItem.prototype, {
		/**
		 * Set the size of the component and its children, called recursively
		 *
		 * @abstract
		 * @returns void
		 */
        setSize: function () {
            throw new Error('Abstract Method');
        },
		/**
		 * Calls a method recursively downwards on the tree
		 *
		 * @param   {String} functionName      the name of the function to be called
		 * @param   {[Array]}functionArguments optional arguments that are passed to every function
		 * @param   {[bool]} bottomUp          Call methods from bottom to top, defaults to false
		 * @param   {[bool]} skipSelf          Don't invoke the method on the class that calls it, defaults to false
		 *
		 * @returns {void}
		 */
        callDownwards: function (functionName, functionArguments, bottomUp, skipSelf) {
            var i;
            if (bottomUp !== true && skipSelf !== true) {
                this[functionName].apply(this, functionArguments || []);
            }
            for (i = 0; i < this.contentItems.length; i++) {
                this.contentItems[i].callDownwards(functionName, functionArguments, bottomUp);
            }
            if (bottomUp === true && skipSelf !== true) {
                this[functionName].apply(this, functionArguments || []);
            }
        },
		/**
		 * Removes a child node (and its children) from the tree
		 *
		 * @param   {lm.items.ContentItem} contentItem
		 *
		 * @returns {void}
		 */
        removeChild: function (contentItem, keepChild) {
			/*
			 * Get the position of the item that's to be removed within all content items this node contains
			 */
            var index = lm.utils.indexOf(contentItem, this.contentItems);
			/*
			 * Make sure the content item to be removed is actually a child of this item
			 */
            if (index === -1) {
                throw new Error('Can\'t remove child item. Unknown content item');
            }
			/**
			 * Call ._$destroy on the content item. This also calls ._$destroy on all its children
			 */
            if (keepChild !== true) {
                this.contentItems[index]._$destroy();
            }
			/**
			 * Remove the content item from this nodes array of children
			 */
            this.contentItems.splice(index, 1);
			/**
			 * Remove the item from the configuration
			 */
            this.config.content.splice(index, 1);
			/**
			 * If this node still contains other content items, adjust their size
			 */
            if (this.contentItems.length > 0) {
                this.callDownwards('setSize');
				/**
				 * If this was the last content item, remove this node as well
				 */
            } else if (!(this instanceof lm.items.Root) && this.config.isClosable === true) {
                this.parent.removeChild(this);
            }
        },
		/**
		 * Sets up the tree structure for the newly added child
		 * The responsibility for the actual DOM manipulations lies
		 * with the concrete item
		 *
		 * @param {lm.items.AbstractContentItem} contentItem
		 * @param {[Int]} index If omitted item will be appended
		 */
        addChild: function (contentItem, index) {
            if (index === undefined) {
                index = this.contentItems.length;
            }
            this.contentItems.splice(index, 0, contentItem);
            if (this.config.content === undefined) {
                this.config.content = [];
            }
            this.config.content.splice(index, 0, contentItem.config);
            contentItem.parent = this;
            if (contentItem.parent.isInitialised === true && contentItem.isInitialised === false) {
                contentItem._$init();
            }
        },
		/**
		 * Replaces oldChild with newChild. This used to use jQuery.replaceWith... which for
		 * some reason removes all event listeners, so isn't really an option.
		 *
		 * @param   {lm.item.AbstractContentItem} oldChild
		 * @param   {lm.item.AbstractContentItem} newChild
		 *
		 * @returns {void}
		 */
        replaceChild: function (oldChild, newChild, _$destroyOldChild) {
            newChild = this.layoutManager._$normalizeContentItem(newChild);
            var index = lm.utils.indexOf(oldChild, this.contentItems),
                parentNode = oldChild.element[0].parentNode;
            if (index === -1) {
                throw new Error('Can\'t replace child. oldChild is not child of this');
            }
            parentNode.replaceChild(newChild.element[0], oldChild.element[0]);
			/*
			 * Optionally destroy the old content item
			 */
            if (_$destroyOldChild === true) {
                oldChild.parent = null;
                oldChild._$destroy();
            }
			/*
			 * Wire the new contentItem into the tree
			 */
            this.contentItems[index] = newChild;
            newChild.parent = this;
			/*
			 * Update tab reference
			 */
            if (this.isStack) {
                this.header.tabs[index].contentItem = newChild;
            }
            //TODO This doesn't update the config... refactor to leave item nodes untouched after creation
            if (newChild.parent.isInitialised === true && newChild.isInitialised === false) {
                newChild._$init();
            }
            this.callDownwards('setSize');
        },
		/**
		 * Convenience method.
		 * Shorthand for this.parent.removeChild( this )
		 *
		 * @returns {void}
		 */
        remove: function () {
            this.parent.removeChild(this);
        },
		/**
		 * Removes the component from the layout and creates a new
		 * browser window with the component and its children inside
		 *
		 * @returns {lm.controls.BrowserPopout}
		 */
        popout: function () {
            var browserPopout = this.layoutManager.createPopout(this);
            this.emitBubblingEvent('stateChanged');
            return browserPopout;
        },
		/**
		 * Maximises the Item or minimises it if it is already maximised
		 *
		 * @returns {void}
		 */
        toggleMaximise: function (e) {
            e && e.preventDefault();
            if (this.isMaximised === true) {
                this.layoutManager._$minimiseItem(this);
            } else {
                this.layoutManager._$maximiseItem(this);
            }
            this.isMaximised = !this.isMaximised;
            this.emitBubblingEvent('stateChanged');
        },
		/**
		 * Selects the item if it is not already selected
		 *
		 * @returns {void}
		 */
        select: function () {
            if (this.layoutManager.selectedItem !== this) {
                this.layoutManager.selectItem(this, true);
                this.element.addClass('lm_selected');
            }
        },
		/**
		 * De-selects the item if it is selected
		 *
		 * @returns {void}
		 */
        deselect: function () {
            if (this.layoutManager.selectedItem === this) {
                this.layoutManager.selectedItem = null;
                this.element.removeClass('lm_selected');
            }
        },
		/**
		 * Set this component's title
		 *
		 * @public
		 * @param {String} title
		 *
		 * @returns {void}
		 */
        setTitle: function (title) {
            this.config.title = title;
            this.emit('titleChanged', title);
            this.emit('stateChanged');
        },
		/**
		 * Checks whether a provided id is present
		 *
		 * @public
		 * @param   {String}  id
		 *
		 * @returns {Boolean} isPresent
		 */
        hasId: function (id) {
            if (!this.config.id) {
                return false;
            } else if (typeof this.config.id === 'string') {
                return this.config.id === id;
            } else if (this.config.id instanceof Array) {
                return lm.utils.indexOf(id, this.config.id) !== -1;
            }
        },
		/**
		 * Adds an id. Adds it as a string if the component doesn't
		 * have an id yet or creates/uses an array
		 *
		 * @public
		 * @param {String} id
		 *
		 * @returns {void}
		 */
        addId: function (id) {
            if (this.hasId(id)) {
                return;
            }
            if (!this.config.id) {
                this.config.id = id;
            } else if (typeof this.config.id === 'string') {
                this.config.id = [this.config.id, id];
            } else if (this.config.id instanceof Array) {
                this.config.id.push(id);
            }
        },
		/**
		 * Removes an existing id. Throws an error
		 * if the id is not present
		 *
		 * @public
		 * @param   {String} id
		 *
		 * @returns {void}
		 */
        removeId: function (id) {
            if (!this.hasId(id)) {
                throw new Error('Id not found');
            }
            if (typeof this.config.id === 'string') {
                delete this.config.id;
            } else if (this.config.id instanceof Array) {
                var index = lm.utils.indexOf(id, this.config.id);
                this.config.id.splice(index, 1);
            }
        },
		/****************************************
		 * SELECTOR
		 ****************************************/
        getItemsByFilter: function (filter) {
            var result = [],
                next = function (contentItem) {
                    for (var i = 0; i < contentItem.contentItems.length; i++) {
                        if (filter(contentItem.contentItems[i]) === true) {
                            result.push(contentItem.contentItems[i]);
                        }
                        next(contentItem.contentItems[i]);
                    }
                };
            next(this);
            return result;
        },
        getItemsById: function (id) {
            return this.getItemsByFilter(function (item) {
                if (item.config.id instanceof Array) {
                    return lm.utils.indexOf(id, item.config.id) !== -1;
                } else {
                    return item.config.id === id;
                }
            });
        },
        getItemsByType: function (type) {
            return this._$getItemsByProperty('type', type);
        },
        getComponentsByName: function (componentName) {
            var components = this._$getItemsByProperty('componentName', componentName),
                instances = [],
                i;
            for (i = 0; i < components.length; i++) {
                instances.push(components[i].instance);
            }
            return instances;
        },
		/****************************************
		 * PACKAGE PRIVATE
		 ****************************************/
        _$getItemsByProperty: function (key, value) {
            return this.getItemsByFilter(function (item) {
                return item[key] === value;
            });
        },
        _$setParent: function (parent) {
            this.parent = parent;
        },
        _$highlightDropZone: function (x, y, area) {
            this.layoutManager.dropTargetIndicator.highlightArea(area);
        },
        _$onDrop: function (contentItem) {
            this.addChild(contentItem);
        },
        _$hide: function () {
            this._callOnActiveComponents('hide');
            this.element.hide();
            this.layoutManager.updateSize();
        },
        _$show: function () {
            this._callOnActiveComponents('show');
            this.element.show();
            this.layoutManager.updateSize();
        },
        _callOnActiveComponents: function (methodName) {
            var stacks = this.getItemsByType('stack'),
                activeContentItem,
                i;
            for (i = 0; i < stacks.length; i++) {
                activeContentItem = stacks[i].getActiveContentItem();
                if (activeContentItem && activeContentItem.isComponent) {
                    activeContentItem.container[methodName]();
                }
            }
        },
		/**
		 * Destroys this item ands its children
		 *
		 * @returns {void}
		 */
        _$destroy: function () {
            this.emitBubblingEvent('beforeItemDestroyed');
            this.callDownwards('_$destroy', [], true, true);
            this.element.remove();
            this.emitBubblingEvent('itemDestroyed');
        },
		/**
		 * Returns the area the component currently occupies in the format
		 *
		 * {
		 *		x1: int
		 *		xy: int
		 *		y1: int
		 *		y2: int
		 *		contentItem: contentItem
		 * }
		 */
        _$getArea: function (element) {
            element = element || this.element;
            var offset = element.offset(),
                width = element.width(),
                height = element.height();
            return {
                x1: offset.left,
                y1: offset.top,
                x2: offset.left + width,
                y2: offset.top + height,
                surface: width * height,
                contentItem: this
            };
        },
		/**
		 * The tree of content items is created in two steps: First all content items are instantiated,
		 * then init is called recursively from top to bottem. This is the basic init function,
		 * it can be used, extended or overwritten by the content items
		 *
		 * Its behaviour depends on the content item
		 *
		 * @package private
		 *
		 * @returns {void}
		 */
        _$init: function () {
            var i;
            this.setSize();
            for (i = 0; i < this.contentItems.length; i++) {
                this.childElementContainer.append(this.contentItems[i].element);
            }
            this.isInitialised = true;
            this.emitBubblingEvent('itemCreated');
            this.emitBubblingEvent(this.type + 'Created');
        },
		/**
		 * Emit an event that bubbles up the item tree.
		 *
		 * @param   {String} name The name of the event
		 *
		 * @returns {void}
		 */
        emitBubblingEvent: function (name) {
            var event = new lm.utils.BubblingEvent(name, this);
            this.emit(name, event);
        },
		/**
		 * Private method, creates all content items for this node at initialisation time
		 * PLEASE NOTE, please see addChild for adding contentItems add runtime
		 * @private
		 * @param   {configuration item node} config
		 *
		 * @returns {void}
		 */
        _createContentItems: function (config) {
            var oContentItem, i;
            if (!(config.content instanceof Array)) {
                throw new lm.errors.ConfigurationError('content must be an Array', config);
            }
            for (i = 0; i < config.content.length; i++) {
                oContentItem = this.layoutManager.createContentItem(config.content[i], this);
                this.contentItems.push(oContentItem);
            }
        },
		/**
		 * Extends an item configuration node with default settings
		 * @private
		 * @param   {configuration item node} config
		 *
		 * @returns {configuration item node} extended config
		 */
        _extendItemNode: function (config) {
            for (var key in lm.config.itemDefaultConfig) {
                if (config[key] === undefined) {
                    config[key] = lm.config.itemDefaultConfig[key];
                }
            }
            return config;
        },
		/**
		 * Called for every event on the item tree. Decides whether the event is a bubbling
		 * event and propagates it to its parent
		 *
		 * @param    {String} name the name of the event
		 * @param   {lm.utils.BubblingEvent} event
		 *
		 * @returns {void}
		 */
        _propagateEvent: function (name, event) {
            if (event instanceof lm.utils.BubblingEvent &&
                event.isPropagationStopped === false &&
                this.isInitialised === true) {
				/**
				 * In some cases (e.g. if an element is created from a DragSource) it
				 * doesn't have a parent and is not below root. If that's the case
				 * propagate the bubbling event from the top level of the substree directly
				 * to the layoutManager
				 */
                if (this.isRoot === false && this.parent) {
                    this.parent.emit.apply(this.parent, Array.prototype.slice.call(arguments, 0));
                } else {
                    this._scheduleEventPropagationToLayoutManager(name, event);
                }
            }
        },
		/**
		 * All raw events bubble up to the root element. Some events that
		 * are propagated to - and emitted by - the layoutManager however are
		 * only string-based, batched and sanitized to make them more usable
		 *
		 * @param {String} name the name of the event
		 *
		 * @private
		 * @returns {void}
		 */
        _scheduleEventPropagationToLayoutManager: function (name, event) {
            if (lm.utils.indexOf(name, this._throttledEvents) === -1) {
                this.layoutManager.emit(name, event.origin);
            } else {
                if (this._pendingEventPropagations[name] !== true) {
                    this._pendingEventPropagations[name] = true;
                    lm.utils.animFrame(lm.utils.fnBind(this._propagateEventToLayoutManager, this, [name, event]));
                }
            }
        },
		/**
		 * Callback for events scheduled by _scheduleEventPropagationToLayoutManager
		 *
		 * @param {String} name the name of the event
		 *
		 * @private
		 * @returns {void}
		 */
        _propagateEventToLayoutManager: function (name, event) {
            this._pendingEventPropagations[name] = false;
            this.layoutManager.emit(name, event);
        }
    });
	/**
	 * @param {[type]} layoutManager [description]
	 * @param {[type]} config      [description]
	 * @param {[type]} parent        [description]
	 */
    lm.items.Component = function (layoutManager, config, parent) {
        lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
        var ComponentConstructor = layoutManager.getComponent(this.config.componentName),
            componentConfig = $.extend(true, {}, this.config.componentState || {});
        componentConfig.componentName = this.config.componentName;
        this.componentName = this.config.componentName;
        if (this.config.title === '') {
            this.config.title = this.config.componentName;
        }
        this.isComponent = true;
        this.container = new lm.container.ItemContainer(this.config, this, layoutManager);
        this.instance = new ComponentConstructor(this.container, componentConfig);
        this.element = this.container._element;
    };
    lm.utils.extend(lm.items.Component, lm.items.AbstractContentItem);
    lm.utils.copy(lm.items.Component.prototype, {
        close: function () {
            this.parent.removeChild(this);
        },
        setSize: function () {
            if (this.element.is(':visible')) {
                // Do not update size of hidden components to prevent unwanted reflows
                this.container._$setSize(this.element.width(), this.element.height());
            }
        },
        _$init: function () {
            lm.items.AbstractContentItem.prototype._$init.call(this);
            this.container.emit('open');
        },
        _$hide: function () {
            this.container.hide();
            lm.items.AbstractContentItem.prototype._$hide.call(this);
        },
        _$show: function () {
            this.container.show();
            lm.items.AbstractContentItem.prototype._$show.call(this);
        },
        _$shown: function () {
            this.container.shown();
            lm.items.AbstractContentItem.prototype._$shown.call(this);
        },
        _$destroy: function () {
            this.container.emit('destroy', this);
            lm.items.AbstractContentItem.prototype._$destroy.call(this);
        },
		/**
		 * Dragging onto a component directly is not an option
		 *
		 * @returns null
		 */
        _$getArea: function () {
            return null;
        }
    });
    lm.items.Root = function (layoutManager, config, containerElement) {
        lm.items.AbstractContentItem.call(this, layoutManager, config, null);
        this.isRoot = true;
        this.type = 'root';
        this.element = $('');
        this.childElementContainer = this.element;
        this._containerElement = containerElement;
        this._containerElement.append(this.element);
    };
    lm.utils.extend(lm.items.Root, lm.items.AbstractContentItem);
    lm.utils.copy(lm.items.Root.prototype, {
        addChild: function (contentItem) {
            if (this.contentItems.length > 0) {
                throw new Error('Root node can only have a single child');
            }
            contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
            this.childElementContainer.append(contentItem.element);
            lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem);
            this.callDownwards('setSize');
            this.emitBubblingEvent('stateChanged');
        },
        setSize: function (width, height) {
            width = (typeof width === 'undefined') ? this._containerElement.width() : width;
            height = (typeof height === 'undefined') ? this._containerElement.height() : height;
            this.element.width(width);
            this.element.height(height);
			/*
			 * Root can be empty
			 */
            if (this.contentItems[0]) {
                this.contentItems[0].element.width(width);
                this.contentItems[0].element.height(height);
            }
        },
        _$highlightDropZone: function (x, y, area) {
            this.layoutManager.tabDropPlaceholder.remove();
            lm.items.AbstractContentItem.prototype._$highlightDropZone.apply(this, arguments);
        },
        _$onDrop: function (contentItem, area) {
            var stack;
            if (contentItem.isComponent) {
                stack = this.layoutManager.createContentItem({
                    type: 'stack',
                    header: contentItem.config.header || {}
                }, this);
                stack._$init();
                stack.addChild(contentItem);
                contentItem = stack;
            }
            if (!this.contentItems.length) {
                this.addChild(contentItem);
            } else {
                var type = area.side[0] == 'x' ? 'row' : 'column';
                var dimension = area.side[0] == 'x' ? 'width' : 'height';
                var insertBefore = area.side[1] == '2';
                var column = this.contentItems[0];
                if (!column instanceof lm.items.RowOrColumn || column.type != type) {
                    var rowOrColumn = this.layoutManager.createContentItem({ type: type }, this);
                    this.replaceChild(column, rowOrColumn);
                    rowOrColumn.addChild(contentItem, insertBefore ? 0 : undefined, true);
                    rowOrColumn.addChild(column, insertBefore ? undefined : 0, true);
                    column.config[dimension] = 50;
                    contentItem.config[dimension] = 50;
                    rowOrColumn.callDownwards('setSize');
                } else {
                    var sibbling = column.contentItems[insertBefore ? 0 : column.contentItems.length - 1]
                    column.addChild(contentItem, insertBefore ? 0 : undefined, true);
                    sibbling.config[dimension] *= 0.5;
                    contentItem.config[dimension] = sibbling.config[dimension];
                    column.callDownwards('setSize');
                }
            }
        }
    });
    lm.items.RowOrColumn = function (isColumn, layoutManager, config, parent) {
        lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
        this.isRow = !isColumn;
        this.isColumn = isColumn;
        this.element = $('');
        this.childElementContainer = this.element;
        this._splitterSize = layoutManager.config.dimensions.borderWidth;
        this._splitterGrabSize = layoutManager.config.dimensions.borderGrabWidth;
        this._isColumn = isColumn;
        this._dimension = isColumn ? 'height' : 'width';
        this._splitter = [];
        this._splitterPosition = null;
        this._splitterMinPosition = null;
        this._splitterMaxPosition = null;
    };
    lm.utils.extend(lm.items.RowOrColumn, lm.items.AbstractContentItem);
    lm.utils.copy(lm.items.RowOrColumn.prototype, {
		/**
		 * Add a new contentItem to the Row or Column
		 *
		 * @param {lm.item.AbstractContentItem} contentItem
		 * @param {[int]} index The position of the new item within the Row or Column.
		 *                      If no index is provided the item will be added to the end
		 * @param {[bool]} _$suspendResize If true the items won't be resized. This will leave the item in
		 *                                 an inconsistent state and is only intended to be used if multiple
		 *                                 children need to be added in one go and resize is called afterwards
		 *
		 * @returns {void}
		 */
        addChild: function (contentItem, index, _$suspendResize) {
            var newItemSize, itemSize, i, splitterElement;
            contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
            if (index === undefined) {
                index = this.contentItems.length;
            }
            if (this.contentItems.length > 0) {
                splitterElement = this._createSplitter(Math.max(0, index - 1)).element;
                if (index > 0) {
                    this.contentItems[index - 1].element.after(splitterElement);
                    splitterElement.after(contentItem.element);
                } else {
                    this.contentItems[0].element.before(splitterElement);
                    splitterElement.before(contentItem.element);
                }
            } else {
                this.childElementContainer.append(contentItem.element);
            }
            lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem, index);
            let fixedItemSize = 0;
            let variableItemCount = 0;
            for (i = 0; i < this.contentItems.length; i++) {
                if (this.contentItems[i].config.fixed)
                    fixedItemSize += this.contentItems[i].config[this._dimension];
                else variableItemCount++;
            }
            newItemSize = (1 / variableItemCount) * (100 - fixedItemSize);
            if (_$suspendResize === true) {
                this.emitBubblingEvent('stateChanged');
                return;
            }
            for (i = 0; i < this.contentItems.length; i++) {
                if (this.contentItems[i].config.fixed)
                    ;
                else if (this.contentItems[i] === contentItem) {
                    contentItem.config[this._dimension] = newItemSize;
                } else {
                    itemSize = this.contentItems[i].config[this._dimension] *= (100 - newItemSize - fixedItemSize) / (100 - fixedItemSize);
                    this.contentItems[i].config[this._dimension] = itemSize;
                }
            }
            this.callDownwards('setSize');
            this.emitBubblingEvent('stateChanged');
        },
		/**
		 * Removes a child of this element
		 *
		 * @param   {lm.items.AbstractContentItem} contentItem
		 * @param   {boolean} keepChild   If true the child will be removed, but not destroyed
		 *
		 * @returns {void}
		 */
        removeChild: function (contentItem, keepChild) {
            var removedItemSize = contentItem.config[this._dimension],
                index = lm.utils.indexOf(contentItem, this.contentItems),
                splitterIndex = Math.max(index - 1, 0),
                i,
                childItem;
            if (index === -1) {
                throw new Error('Can\'t remove child. ContentItem is not child of this Row or Column');
            }
			/**
			 * Remove the splitter before the item or after if the item happens
			 * to be the first in the row/column
			 */
            if (this._splitter[splitterIndex]) {
                this._splitter[splitterIndex]._$destroy();
                this._splitter.splice(splitterIndex, 1);
            }
            let fixedItemSize = 0;
            for (i = 0; i < this.contentItems.length; i++) {
                if (this.contentItems[i].config.fixed)
                    fixedItemSize += this.contentItems[i].config[this._dimension];
            }
			/**
			 * Allocate the space that the removed item occupied to the remaining items
			 */
            for (i = 0; i < this.contentItems.length; i++) {
                if (this.contentItems[i].config.fixed)
                    ;
                else if (this.contentItems[i] !== contentItem) {
                    this.contentItems[i].config[this._dimension] *= (100 - fixedItemSize) / (100 - removedItemSize - fixedItemSize);
                }
            }
            lm.items.AbstractContentItem.prototype.removeChild.call(this, contentItem, keepChild);
            if (this.contentItems.length === 1 && this.config.isClosable === true) {
                childItem = this.contentItems[0];
                this.contentItems = [];
                this.parent.replaceChild(this, childItem, true);
            } else {
                this.callDownwards('setSize');
                this.emitBubblingEvent('stateChanged');
            }
        },
		/**
		 * Replaces a child of this Row or Column with another contentItem
		 *
		 * @param   {lm.items.AbstractContentItem} oldChild
		 * @param   {lm.items.AbstractContentItem} newChild
		 *
		 * @returns {void}
		 */
        replaceChild: function (oldChild, newChild) {
            var size = oldChild.config[this._dimension];
            lm.items.AbstractContentItem.prototype.replaceChild.call(this, oldChild, newChild);
            newChild.config[this._dimension] = size;
            this.callDownwards('setSize');
            this.emitBubblingEvent('stateChanged');
        },
		/**
		 * Called whenever the dimensions of this item or one of its parents change
		 *
		 * @returns {void}
		 */
        setSize: function () {
            if (this.contentItems.length > 0) {
                this._calculateRelativeSizes();
                this._setAbsoluteSizes();
            }
            this.emitBubblingEvent('stateChanged');
            this.emit('resize');
        },
		/**
		 * Invoked recursively by the layout manager. AbstractContentItem.init appends
		 * the contentItem's DOM elements to the container, RowOrColumn init adds splitters
		 * in between them
		 *
		 * @package private
		 * @override AbstractContentItem._$init
		 * @returns {void}
		 */
        _$init: function () {
            if (this.isInitialised === true) return;
            var i;
            lm.items.AbstractContentItem.prototype._$init.call(this);
            for (i = 0; i < this.contentItems.length - 1; i++) {
                this.contentItems[i].element.after(this._createSplitter(i).element);
            }
        },
		/**
		 * Turns the relative sizes calculated by _calculateRelativeSizes into
		 * absolute pixel values and applies them to the children's DOM elements
		 *
		 * Assigns additional pixels to counteract Math.floor
		 *
		 * @private
		 * @returns {void}
		 */
        _setAbsoluteSizes: function () {
            var i,
                sizeData = this._calculateAbsoluteSizes();
            for (i = 0; i < this.contentItems.length; i++) {
                if (sizeData.additionalPixel - i > 0) {
                    sizeData.itemSizes[i]++;
                }
                if (this._isColumn) {
                    this.contentItems[i].element.width(sizeData.totalWidth);
                    this.contentItems[i].element.height(sizeData.itemSizes[i]);
                } else {
                    this.contentItems[i].element.width(sizeData.itemSizes[i]);
                    this.contentItems[i].element.height(sizeData.totalHeight);
                }
            }
        },
		/**
		 * Calculates the absolute sizes of all of the children of this Item.
		 * @returns {object} - Set with absolute sizes and additional pixels.
		 */
        _calculateAbsoluteSizes: function () {
            var i,
                totalSplitterSize = (this.contentItems.length - 1) * this._splitterSize,
                totalWidth = this.element.width(),
                totalHeight = this.element.height(),
                totalAssigned = 0,
                additionalPixel,
                itemSize,
                itemSizes = [];
            if (this._isColumn) {
                totalHeight -= totalSplitterSize;
            } else {
                totalWidth -= totalSplitterSize;
            }
            for (i = 0; i < this.contentItems.length; i++) {
                if (this._isColumn) {
                    itemSize = Math.floor(totalHeight * (this.contentItems[i].config.height / 100));
                } else {
                    itemSize = Math.floor(totalWidth * (this.contentItems[i].config.width / 100));
                }
                totalAssigned += itemSize;
                itemSizes.push(itemSize);
            }
            additionalPixel = Math.floor((this._isColumn ? totalHeight : totalWidth) - totalAssigned);
            return {
                itemSizes: itemSizes,
                additionalPixel: additionalPixel,
                totalWidth: totalWidth,
                totalHeight: totalHeight
            };
        },
		/**
		 * Calculates the relative sizes of all children of this Item. The logic
		 * is as follows:
		 *
		 * - Add up the total size of all items that have a configured size
		 *
		 * - If the total == 100 (check for floating point errors)
		 *        Excellent, job done
		 *
		 * - If the total is > 100,
		 *        set the size of items without set dimensions to 1/3 and add this to the total
		 *        set the size off all items so that the total is hundred relative to their original size
		 *
		 * - If the total is < 100
		 *        If there are items without set dimensions, distribute the remainder to 100 evenly between them
		 *        If there are no items without set dimensions, increase all items sizes relative to
		 *        their original size so that they add up to 100
		 *
		 * @private
		 * @returns {void}
		 */
        _calculateRelativeSizes: function () {
            var i,
                total = 0,
                itemsWithoutSetDimension = [],
                dimension = this._isColumn ? 'height' : 'width';
            for (i = 0; i < this.contentItems.length; i++) {
                if (this.contentItems[i].config[dimension] !== undefined) {
                    total += this.contentItems[i].config[dimension];
                } else {
                    itemsWithoutSetDimension.push(this.contentItems[i]);
                }
            }
			/**
			 * Everything adds up to hundred, all good :-)
			 */
            if (Math.round(total) === 100) {
                this._respectMinItemWidth();
                return;
            }
			/**
			 * Allocate the remaining size to the items without a set dimension
			 */
            if (Math.round(total) < 100 && itemsWithoutSetDimension.length > 0) {
                for (i = 0; i < itemsWithoutSetDimension.length; i++) {
                    itemsWithoutSetDimension[i].config[dimension] = (100 - total) / itemsWithoutSetDimension.length;
                }
                this._respectMinItemWidth();
                return;
            }
			/**
			 * If the total is > 100, but there are also items without a set dimension left, assing 50
			 * as their dimension and add it to the total
			 *
			 * This will be reset in the next step
			 */
            if (Math.round(total) > 100) {
                for (i = 0; i < itemsWithoutSetDimension.length; i++) {
                    itemsWithoutSetDimension[i].config[dimension] = 50;
                    total += 50;
                }
            }
			/**
			 * Set every items size relative to 100 relative to its size to total
			 */
            for (i = 0; i < this.contentItems.length; i++) {
                this.contentItems[i].config[dimension] = (this.contentItems[i].config[dimension] / total) * 100;
            }
            this._respectMinItemWidth();
        },
		/**
		 * Adjusts the column widths to respect the dimensions minItemWidth if set.
		 * @returns {}
		 */
        _respectMinItemWidth: function () {
            var minItemWidth = this.layoutManager.config.dimensions ? (this.layoutManager.config.dimensions.minItemWidth || 0) : 0,
                sizeData = null,
                entriesOverMin = [],
                totalOverMin = 0,
                totalUnderMin = 0,
                remainingWidth = 0,
                itemSize = 0,
                contentItem = null,
                reducePercent,
                reducedWidth,
                allEntries = [],
                entry;
            if (this._isColumn || !minItemWidth || this.contentItems.length <= 1) {
                return;
            }
            sizeData = this._calculateAbsoluteSizes();
			/**
			 * Figure out how much we are under the min item size total and how much room we have to use.
			 */
            for (var i = 0; i < this.contentItems.length; i++) {
                contentItem = this.contentItems[i];
                itemSize = sizeData.itemSizes[i];
                if (itemSize < minItemWidth) {
                    totalUnderMin += minItemWidth - itemSize;
                    entry = { width: minItemWidth };
                }
                else {
                    totalOverMin += itemSize - minItemWidth;
                    entry = { width: itemSize };
                    entriesOverMin.push(entry);
                }
                allEntries.push(entry);
            }
			/**
			 * If there is nothing under min, or there is not enough over to make up the difference, do nothing.
			 */
            if (totalUnderMin === 0 || totalUnderMin > totalOverMin) {
                return;
            }
			/**
			 * Evenly reduce all columns that are over the min item width to make up the difference.
			 */
            reducePercent = totalUnderMin / totalOverMin;
            remainingWidth = totalUnderMin;
            for (i = 0; i < entriesOverMin.length; i++) {
                entry = entriesOverMin[i];
                reducedWidth = Math.round((entry.width - minItemWidth) * reducePercent);
                remainingWidth -= reducedWidth;
                entry.width -= reducedWidth;
            }
			/**
			 * Take anything remaining from the last item.
			 */
            if (remainingWidth !== 0) {
                allEntries[allEntries.length - 1].width -= remainingWidth;
            }
			/**
			 * Set every items size relative to 100 relative to its size to total
			 */
            for (i = 0; i < this.contentItems.length; i++) {
                this.contentItems[i].config.width = (allEntries[i].width / sizeData.totalWidth) * 100;
            }
        },
		/**
		 * Instantiates a new lm.controls.Splitter, binds events to it and adds
		 * it to the array of splitters at the position specified as the index argument
		 *
		 * What it doesn't do though is append the splitter to the DOM
		 *
		 * @param   {Int} index The position of the splitter
		 *
		 * @returns {lm.controls.Splitter}
		 */
        _createSplitter: function (index) {
            var splitter;
            splitter = new lm.controls.Splitter(this._isColumn, this._splitterSize, this._splitterGrabSize);
            splitter.on('drag', lm.utils.fnBind(this._onSplitterDrag, this, [splitter]), this);
            splitter.on('dragStop', lm.utils.fnBind(this._onSplitterDragStop, this, [splitter]), this);
            splitter.on('dragStart', lm.utils.fnBind(this._onSplitterDragStart, this, [splitter]), this);
            this._splitter.splice(index, 0, splitter);
            return splitter;
        },
		/**
		 * Locates the instance of lm.controls.Splitter in the array of
		 * registered splitters and returns a map containing the contentItem
		 * before and after the splitters, both of which are affected if the
		 * splitter is moved
		 *
		 * @param   {lm.controls.Splitter} splitter
		 *
		 * @returns {Object} A map of contentItems that the splitter affects
		 */
        _getItemsForSplitter: function (splitter) {
            var index = lm.utils.indexOf(splitter, this._splitter);
            return {
                before: this.contentItems[index],
                after: this.contentItems[index + 1]
            };
        },
		/**
		 * Gets the minimum dimensions for the given item configuration array
		 * @param item
		 * @private
		 */
        _getMinimumDimensions: function (arr) {
            var minWidth = 0, minHeight = 0;
            for (var i = 0; i < arr.length; ++i) {
                minWidth = Math.max(arr[i].minWidth || 0, minWidth);
                minHeight = Math.max(arr[i].minHeight || 0, minHeight);
            }
            return { horizontal: minWidth, vertical: minHeight };
        },
		/**
		 * Invoked when a splitter's dragListener fires dragStart. Calculates the splitters
		 * movement area once (so that it doesn't need calculating on every mousemove event)
		 *
		 * @param   {lm.controls.Splitter} splitter
		 *
		 * @returns {void}
		 */
        _onSplitterDragStart: function (splitter) {
            var items = this._getItemsForSplitter(splitter),
                minSize = this.layoutManager.config.dimensions[this._isColumn ? 'minItemHeight' : 'minItemWidth'];
            var beforeMinDim = this._getMinimumDimensions(items.before.config.content);
            var beforeMinSize = this._isColumn ? beforeMinDim.vertical : beforeMinDim.horizontal;
            var afterMinDim = this._getMinimumDimensions(items.after.config.content);
            var afterMinSize = this._isColumn ? afterMinDim.vertical : afterMinDim.horizontal;
            this._splitterPosition = 0;
            this._splitterMinPosition = -1 * (items.before.element[this._dimension]() - (beforeMinSize || minSize));
            this._splitterMaxPosition = items.after.element[this._dimension]() - (afterMinSize || minSize);
        },
		/**
		 * Invoked when a splitter's DragListener fires drag. Updates the splitters DOM position,
		 * but not the sizes of the elements the splitter controls in order to minimize resize events
		 *
		 * @param   {lm.controls.Splitter} splitter
		 * @param   {Int} offsetX  Relative pixel values to the splitters original position. Can be negative
		 * @param   {Int} offsetY  Relative pixel values to the splitters original position. Can be negative
		 *
		 * @returns {void}
		 */
        _onSplitterDrag: function (splitter, offsetX, offsetY) {
            var offset = this._isColumn ? offsetY : offsetX;
            if (offset > this._splitterMinPosition && offset < this._splitterMaxPosition) {
                this._splitterPosition = offset;
                splitter.element.css(this._isColumn ? 'top' : 'left', offset);
            }
        },
		/**
		 * Invoked when a splitter's DragListener fires dragStop. Resets the splitters DOM position,
		 * and applies the new sizes to the elements before and after the splitter and their children
		 * on the next animation frame
		 *
		 * @param   {lm.controls.Splitter} splitter
		 *
		 * @returns {void}
		 */
        _onSplitterDragStop: function (splitter) {
            var items = this._getItemsForSplitter(splitter),
                sizeBefore = items.before.element[this._dimension](),
                sizeAfter = items.after.element[this._dimension](),
                splitterPositionInRange = (this._splitterPosition + sizeBefore) / (sizeBefore + sizeAfter),
                totalRelativeSize = items.before.config[this._dimension] + items.after.config[this._dimension];
            items.before.config[this._dimension] = splitterPositionInRange * totalRelativeSize;
            items.after.config[this._dimension] = (1 - splitterPositionInRange) * totalRelativeSize;
            splitter.element.css({
                'top': 0,
                'left': 0
            });
            lm.utils.animFrame(lm.utils.fnBind(this.callDownwards, this, ['setSize']));
        }
    });
    lm.items.Stack = function (layoutManager, config, parent) {
        lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
        this.element = $('');
        this._activeContentItem = null;
        var cfg = layoutManager.config;
        this._header = { // defaults' reconstruction from old configuration style
            show: cfg.settings.hasHeaders === true && config.hasHeaders !== false,
            popout: cfg.settings.showPopoutIcon && cfg.labels.popout,
            maximise: cfg.settings.showMaximiseIcon && cfg.labels.maximise,
            close: cfg.settings.showCloseIcon && cfg.labels.close,
            minimise: cfg.labels.minimise,
        };
        if (cfg.header) // load simplified version of header configuration (https://github.com/deepstreamIO/golden-layout/pull/245)
            lm.utils.copy(this._header, cfg.header);
        if (config.header) // load from stack
            lm.utils.copy(this._header, config.header);
        if (config.content && config.content[0] && config.content[0].header) // load from component if stack omitted
            lm.utils.copy(this._header, config.content[0].header);
        this._dropZones = {};
        this._dropSegment = null;
        this._contentAreaDimensions = null;
        this._dropIndex = null;
        this.isStack = true;
        this.childElementContainer = $('');
        this.header = new lm.controls.Header(layoutManager, this);
        this.element.append(this.header.element);
        this.element.append(this.childElementContainer);
        this._setupHeaderPosition();
        this._$validateClosability();
    };
    lm.utils.extend(lm.items.Stack, lm.items.AbstractContentItem);
    lm.utils.copy(lm.items.Stack.prototype, {
        setSize: function () {
            var i,
                headerSize = this._header.show ? this.layoutManager.config.dimensions.headerHeight : 0,
                contentWidth = this.element.width() - (this._sided ? headerSize : 0),
                contentHeight = this.element.height() - (!this._sided ? headerSize : 0);
            this.childElementContainer.width(contentWidth);
            this.childElementContainer.height(contentHeight);
            for (i = 0; i < this.contentItems.length; i++) {
                this.contentItems[i].element.width(contentWidth).height(contentHeight);
            }
            this.emit('resize');
            this.emitBubblingEvent('stateChanged');
        },
        _$init: function () {
            var i, initialItem;
            if (this.isInitialised === true) return;
            lm.items.AbstractContentItem.prototype._$init.call(this);
            for (i = 0; i < this.contentItems.length; i++) {
                this.header.createTab(this.contentItems[i]);
                this.contentItems[i]._$hide();
            }
            if (this.contentItems.length > 0) {
                initialItem = this.contentItems[Math.min(this.contentItems.length - 1, this.config.activeItemIndex || 0)];
                if (!initialItem) {
                    throw new Error('Configured activeItemIndex out of bounds');
                }
                this.setActiveContentItem(initialItem);
            }
        },
        setActiveContentItem: function (contentItem) {
            if (lm.utils.indexOf(contentItem, this.contentItems) === -1) {
                throw new Error('contentItem is not a child of this stack');
            }
            if (this._activeContentItem !== null) {
                this._activeContentItem._$hide();
            }
            this._activeContentItem = contentItem;
            this.header.setActiveContentItem(contentItem);
            contentItem._$show();
            this.emit('activeContentItemChanged', contentItem);
            this.layoutManager.emit('activeContentItemChanged', contentItem);
            this.emitBubblingEvent('stateChanged');
        },
        getActiveContentItem: function () {
            return this.header.activeContentItem;
        },
        addChild: function (contentItem, index) {
            contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
            lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem, index);
            this.childElementContainer.append(contentItem.element);
            this.header.createTab(contentItem, index);
            this.setActiveContentItem(contentItem);
            this.callDownwards('setSize');
            this._$validateClosability();
            this.emitBubblingEvent('stateChanged');
        },
        removeChild: function (contentItem, keepChild) {
            var index = lm.utils.indexOf(contentItem, this.contentItems);
            lm.items.AbstractContentItem.prototype.removeChild.call(this, contentItem, keepChild);
            this.header.removeTab(contentItem);
            if (this.header.activeContentItem === contentItem) {
                if (this.contentItems.length > 0) {
                    this.setActiveContentItem(this.contentItems[Math.max(index - 1, 0)]);
                } else {
                    this._activeContentItem = null;
                }
            }
            this._$validateClosability();
            this.emitBubblingEvent('stateChanged');
        },
		/**
		 * Validates that the stack is still closable or not. If a stack is able
		 * to close, but has a non closable component added to it, the stack is no
		 * longer closable until all components are closable.
		 *
		 * @returns {void}
		 */
        _$validateClosability: function () {
            var contentItem,
                isClosable,
                len,
                i;
            isClosable = this.header._isClosable();
            for (i = 0, len = this.contentItems.length; i < len; i++) {
                if (!isClosable) {
                    break;
                }
                isClosable = this.contentItems[i].config.isClosable;
            }
            this.header._$setClosable(isClosable);
        },
        _$destroy: function () {
            lm.items.AbstractContentItem.prototype._$destroy.call(this);
            this.header._$destroy();
        },
		/**
		 * Ok, this one is going to be the tricky one: The user has dropped {contentItem} onto this stack.
		 *
		 * It was dropped on either the stacks header or the top, right, bottom or left bit of the content area
		 * (which one of those is stored in this._dropSegment). Now, if the user has dropped on the header the case
		 * is relatively clear: We add the item to the existing stack... job done (might be good to have
		 * tab reordering at some point, but lets not sweat it right now)
		 *
		 * If the item was dropped on the content part things are a bit more complicated. If it was dropped on either the
		 * top or bottom region we need to create a new column and place the items accordingly.
		 * Unless, of course if the stack is already within a column... in which case we want
		 * to add the newly created item to the existing column...
		 * either prepend or append it, depending on wether its top or bottom.
		 *
		 * Same thing for rows and left / right drop segments... so in total there are 9 things that can potentially happen
		 * (left, top, right, bottom) * is child of the right parent (row, column) + header drop
		 *
		 * @param    {lm.item} contentItem
		 *
		 * @returns {void}
		 */
        _$onDrop: function (contentItem) {
			/*
			 * The item was dropped on the header area. Just add it as a child of this stack and
			 * get the hell out of this logic
			 */
            if (this._dropSegment === 'header') {
                this._resetHeaderDropZone();
                this.addChild(contentItem, this._dropIndex);
                return;
            }
			/*
			 * The stack is empty. Let's just add the element.
			 */
            if (this._dropSegment === 'body') {
                this.addChild(contentItem);
                return;
            }
			/*
			 * The item was dropped on the top-, left-, bottom- or right- part of the content. Let's
			 * aggregate some conditions to make the if statements later on more readable
			 */
            var isVertical = this._dropSegment === 'top' || this._dropSegment === 'bottom',
                isHorizontal = this._dropSegment === 'left' || this._dropSegment === 'right',
                insertBefore = this._dropSegment === 'top' || this._dropSegment === 'left',
                hasCorrectParent = (isVertical && this.parent.isColumn) || (isHorizontal && this.parent.isRow),
                type = isVertical ? 'column' : 'row',
                dimension = isVertical ? 'height' : 'width',
                index,
                stack,
                rowOrColumn;
			/*
			 * The content item can be either a component or a stack. If it is a component, wrap it into a stack
			 */
            if (contentItem.isComponent) {
                stack = this.layoutManager.createContentItem({
                    type: 'stack',
                    header: contentItem.config.header || {}
                }, this);
                stack._$init();
                stack.addChild(contentItem);
                contentItem = stack;
            }
			/*
			 * If the item is dropped on top or bottom of a column or left and right of a row, it's already
			 * layd out in the correct way. Just add it as a child
			 */
            if (hasCorrectParent) {
                index = lm.utils.indexOf(this, this.parent.contentItems);
                this.parent.addChild(contentItem, insertBefore ? index : index + 1, true);
                this.config[dimension] *= 0.5;
                contentItem.config[dimension] = this.config[dimension];
                this.parent.callDownwards('setSize');
				/*
				 * This handles items that are dropped on top or bottom of a row or left / right of a column. We need
				 * to create the appropriate contentItem for them to live in
				 */
            } else {
                type = isVertical ? 'column' : 'row';
                rowOrColumn = this.layoutManager.createContentItem({ type: type }, this);
                this.parent.replaceChild(this, rowOrColumn);
                rowOrColumn.addChild(contentItem, insertBefore ? 0 : undefined, true);
                rowOrColumn.addChild(this, insertBefore ? undefined : 0, true);
                this.config[dimension] = 50;
                contentItem.config[dimension] = 50;
                rowOrColumn.callDownwards('setSize');
            }
        },
		/**
		 * If the user hovers above the header part of the stack, indicate drop positions for tabs.
		 * otherwise indicate which segment of the body the dragged item would be dropped on
		 *
		 * @param    {Int} x Absolute Screen X
		 * @param    {Int} y Absolute Screen Y
		 *
		 * @returns {void}
		 */
        _$highlightDropZone: function (x, y) {
            var segment, area;
            for (segment in this._contentAreaDimensions) {
                area = this._contentAreaDimensions[segment].hoverArea;
                if (area.x1 < x && area.x2 > x && area.y1 < y && area.y2 > y) {
                    if (segment === 'header') {
                        this._dropSegment = 'header';
                        this._highlightHeaderDropZone(this._sided ? y : x);
                    } else {
                        this._resetHeaderDropZone();
                        this._highlightBodyDropZone(segment);
                    }
                    return;
                }
            }
        },
        _$getArea: function () {
            if (this.element.is(':visible') === false) {
                return null;
            }
            var getArea = lm.items.AbstractContentItem.prototype._$getArea,
                headerArea = getArea.call(this, this.header.element),
                contentArea = getArea.call(this, this.childElementContainer),
                contentWidth = contentArea.x2 - contentArea.x1,
                contentHeight = contentArea.y2 - contentArea.y1;
            this._contentAreaDimensions = {
                header: {
                    hoverArea: {
                        x1: headerArea.x1,
                        y1: headerArea.y1,
                        x2: headerArea.x2,
                        y2: headerArea.y2
                    },
                    highlightArea: {
                        x1: headerArea.x1,
                        y1: headerArea.y1,
                        x2: headerArea.x2,
                        y2: headerArea.y2
                    }
                }
            };
			/**
			 * If this Stack is a parent to rows, columns or other stacks only its
			 * header is a valid dropzone.
			 */
            if (this._activeContentItem && this._activeContentItem.isComponent === false) {
                return headerArea;
            }
			/**
			 * Highlight the entire body if the stack is empty
			 */
            if (this.contentItems.length === 0) {
                this._contentAreaDimensions.body = {
                    hoverArea: {
                        x1: contentArea.x1,
                        y1: contentArea.y1,
                        x2: contentArea.x2,
                        y2: contentArea.y2
                    },
                    highlightArea: {
                        x1: contentArea.x1,
                        y1: contentArea.y1,
                        x2: contentArea.x2,
                        y2: contentArea.y2
                    }
                };
                return getArea.call(this, this.element);
            }
            this._contentAreaDimensions.left = {
                hoverArea: {
                    x1: contentArea.x1,
                    y1: contentArea.y1,
                    x2: contentArea.x1 + contentWidth * 0.25,
                    y2: contentArea.y2
                },
                highlightArea: {
                    x1: contentArea.x1,
                    y1: contentArea.y1,
                    x2: contentArea.x1 + contentWidth * 0.5,
                    y2: contentArea.y2
                }
            };
            this._contentAreaDimensions.top = {
                hoverArea: {
                    x1: contentArea.x1 + contentWidth * 0.25,
                    y1: contentArea.y1,
                    x2: contentArea.x1 + contentWidth * 0.75,
                    y2: contentArea.y1 + contentHeight * 0.5
                },
                highlightArea: {
                    x1: contentArea.x1,
                    y1: contentArea.y1,
                    x2: contentArea.x2,
                    y2: contentArea.y1 + contentHeight * 0.5
                }
            };
            this._contentAreaDimensions.right = {
                hoverArea: {
                    x1: contentArea.x1 + contentWidth * 0.75,
                    y1: contentArea.y1,
                    x2: contentArea.x2,
                    y2: contentArea.y2
                },
                highlightArea: {
                    x1: contentArea.x1 + contentWidth * 0.5,
                    y1: contentArea.y1,
                    x2: contentArea.x2,
                    y2: contentArea.y2
                }
            };
            this._contentAreaDimensions.bottom = {
                hoverArea: {
                    x1: contentArea.x1 + contentWidth * 0.25,
                    y1: contentArea.y1 + contentHeight * 0.5,
                    x2: contentArea.x1 + contentWidth * 0.75,
                    y2: contentArea.y2
                },
                highlightArea: {
                    x1: contentArea.x1,
                    y1: contentArea.y1 + contentHeight * 0.5,
                    x2: contentArea.x2,
                    y2: contentArea.y2
                }
            };
            return getArea.call(this, this.element);
        },
        _highlightHeaderDropZone: function (x) {
            var i,
                tabElement,
                tabsLength = this.header.tabs.length,
                isAboveTab = false,
                tabTop,
                tabLeft,
                offset,
                placeHolderLeft,
                headerOffset,
                tabWidth,
                halfX;
            // Empty stack
            if (tabsLength === 0) {
                headerOffset = this.header.element.offset();
                this.layoutManager.dropTargetIndicator.highlightArea({
                    x1: headerOffset.left,
                    x2: headerOffset.left + 100,
                    y1: headerOffset.top + this.header.element.height() - 20,
                    y2: headerOffset.top + this.header.element.height()
                });
                return;
            }
            for (i = 0; i < tabsLength; i++) {
                tabElement = this.header.tabs[i].element;
                offset = tabElement.offset();
                if (this._sided) {
                    tabLeft = offset.top;
                    tabTop = offset.left;
                    tabWidth = tabElement.height();
                } else {
                    tabLeft = offset.left;
                    tabTop = offset.top;
                    tabWidth = tabElement.width();
                }
                if (x > tabLeft && x < tabLeft + tabWidth) {
                    isAboveTab = true;
                    break;
                }
            }
            if (isAboveTab === false && x < tabLeft) {
                return;
            }
            halfX = tabLeft + tabWidth / 2;
            if (x < halfX) {
                this._dropIndex = i;
                tabElement.before(this.layoutManager.tabDropPlaceholder);
            } else {
                this._dropIndex = Math.min(i + 1, tabsLength);
                tabElement.after(this.layoutManager.tabDropPlaceholder);
            }
            if (this._sided) {
                placeHolderTop = this.layoutManager.tabDropPlaceholder.offset().top;
                this.layoutManager.dropTargetIndicator.highlightArea({
                    x1: tabTop,
                    x2: tabTop + tabElement.innerHeight(),
                    y1: placeHolderTop,
                    y2: placeHolderTop + this.layoutManager.tabDropPlaceholder.width()
                });
                return;
            }
            placeHolderLeft = this.layoutManager.tabDropPlaceholder.offset().left;
            this.layoutManager.dropTargetIndicator.highlightArea({
                x1: placeHolderLeft,
                x2: placeHolderLeft + this.layoutManager.tabDropPlaceholder.width(),
                y1: tabTop,
                y2: tabTop + tabElement.innerHeight()
            });
        },
        _resetHeaderDropZone: function () {
            this.layoutManager.tabDropPlaceholder.remove();
        },
        _setupHeaderPosition: function () {
            var side = ['right', 'left', 'bottom'].indexOf(this._header.show) >= 0 && this._header.show;
            this.header.element.toggle(!!this._header.show);
            this._side = side;
            this._sided = ['right', 'left'].indexOf(this._side) >= 0;
            this.element.removeClass('lm_left lm_right lm_bottom');
            if (this._side)
                this.element.addClass('lm_' + this._side);
            if (this.element.find('.lm_header').length && this.childElementContainer) {
                var headerPosition = ['right', 'bottom'].indexOf(this._side) >= 0 ? 'before' : 'after';
                this.header.element[headerPosition](this.childElementContainer);
                this.callDownwards('setSize');
            }
        },
        _highlightBodyDropZone: function (segment) {
            var highlightArea = this._contentAreaDimensions[segment].highlightArea;
            this.layoutManager.dropTargetIndicator.highlightArea(highlightArea);
            this._dropSegment = segment;
        }
    });
    lm.utils.BubblingEvent = function (name, origin) {
        this.name = name;
        this.origin = origin;
        this.isPropagationStopped = false;
    };
    lm.utils.BubblingEvent.prototype.stopPropagation = function () {
        this.isPropagationStopped = true;
    };
	/**
	 * Minifies and unminifies configs by replacing frequent keys
	 * and values with one letter substitutes. Config options must
	 * retain array position/index, add new options at the end.
	 *
	 * @constructor
	 */
    lm.utils.ConfigMinifier = function () {
        this._keys = [
            'settings',
            'hasHeaders',
            'constrainDragToContainer',
            'selectionEnabled',
            'dimensions',
            'borderWidth',
            'minItemHeight',
            'minItemWidth',
            'headerHeight',
            'dragProxyWidth',
            'dragProxyHeight',
            'labels',
            'close',
            'maximise',
            'minimise',
            'popout',
            'content',
            'componentName',
            'componentState',
            'id',
            'width',
            'type',
            'height',
            'isClosable',
            'title',
            'popoutWholeStack',
            'openPopouts',
            'parentId',
            'activeItemIndex',
            'reorderEnabled',
            'borderGrabWidth',
            //Maximum 36 entries, do not cross this line!
        ];
        if (this._keys.length > 36) {
            throw new Error('Too many keys in config minifier map');
        }
        this._values = [
            true,
            false,
            'row',
            'column',
            'stack',
            'component',
            'close',
            'maximise',
            'minimise',
            'open in new window'
        ];
    };
    lm.utils.copy(lm.utils.ConfigMinifier.prototype, {
		/**
		 * Takes a GoldenLayout configuration object and
		 * replaces its keys and values recursively with
		 * one letter counterparts
		 *
		 * @param   {Object} config A GoldenLayout config object
		 *
		 * @returns {Object} minified config
		 */
        minifyConfig: function (config) {
            var min = {};
            this._nextLevel(config, min, '_min');
            return min;
        },
		/**
		 * Takes a configuration Object that was previously minified
		 * using minifyConfig and returns its original version
		 *
		 * @param   {Object} minifiedConfig
		 *
		 * @returns {Object} the original configuration
		 */
        unminifyConfig: function (minifiedConfig) {
            var orig = {};
            this._nextLevel(minifiedConfig, orig, '_max');
            return orig;
        },
		/**
		 * Recursive function, called for every level of the config structure
		 *
		 * @param   {Array|Object} orig
		 * @param   {Array|Object} min
		 * @param    {String} translationFn
		 *
		 * @returns {void}
		 */
        _nextLevel: function (from, to, translationFn) {
            var key, minKey;
            for (key in from) {
				/**
				 * For in returns array indices as keys, so let's cast them to numbers
				 */
                if (from instanceof Array) key = parseInt(key, 10);
				/**
				 * In case something has extended Object prototypes
				 */
                if (!from.hasOwnProperty(key)) continue;
				/**
				 * Translate the key to a one letter substitute
				 */
                minKey = this[translationFn](key, this._keys);
				/**
				 * For Arrays and Objects, create a new Array/Object
				 * on the minified object and recurse into it
				 */
                if (typeof from[key] === 'object') {
                    to[minKey] = from[key] instanceof Array ? [] : {};
                    this._nextLevel(from[key], to[minKey], translationFn);
					/**
					 * For primitive values (Strings, Numbers, Boolean etc.)
					 * minify the value
					 */
                } else {
                    to[minKey] = this[translationFn](from[key], this._values);
                }
            }
        },
		/**
		 * Minifies value based on a dictionary
		 *
		 * @param   {String|Boolean} value
		 * @param   {Array} dictionary
		 *
		 * @returns {String} The minified version
		 */
        _min: function (value, dictionary) {
			/**
			 * If a value actually is a single character, prefix it
			 * with ___ to avoid mistaking it for a minification code
			 */
            if (typeof value === 'string' && value.length === 1) {
                return '___' + value;
            }
            var index = lm.utils.indexOf(value, dictionary);
			/**
			 * value not found in the dictionary, return it unmodified
			 */
            if (index === -1) {
                return value;
				/**
				 * value found in dictionary, return its base36 counterpart
				 */
            } else {
                return index.toString(36);
            }
        },
        _max: function (value, dictionary) {
			/**
			 * value is a single character. Assume that it's a translation
			 * and return the original value from the dictionary
			 */
            if (typeof value === 'string' && value.length === 1) {
                return dictionary[parseInt(value, 36)];
            }
			/**
			 * value originally was a single character and was prefixed with ___
			 * to avoid mistaking it for a translation. Remove the prefix
			 * and return the original character
			 */
            if (typeof value === 'string' && value.substr(0, 3) === '___') {
                return value[3];
            }
			/**
			 * value was not minified
			 */
            return value;
        }
    });
	/**
	 * An EventEmitter singleton that propagates events
	 * across multiple windows. This is a little bit trickier since
	 * windows are allowed to open childWindows in their own right
	 *
	 * This means that we deal with a tree of windows. Hence the rules for event propagation are:
	 *
	 * - Propagate events from this layout to both parents and children
	 * - Propagate events from parent to this and children
	 * - Propagate events from children to the other children (but not the emitting one) and the parent
	 *
	 * @constructor
	 *
	 * @param {lm.LayoutManager} layoutManager
	 */
    lm.utils.EventHub = function (layoutManager) {
        lm.utils.EventEmitter.call(this);
        this._layoutManager = layoutManager;
        this._dontPropagateToParent = null;
        this._childEventSource = null;
        this.on(lm.utils.EventEmitter.ALL_EVENT, lm.utils.fnBind(this._onEventFromThis, this));
        this._boundOnEventFromChild = lm.utils.fnBind(this._onEventFromChild, this);
        $(window).on('gl_child_event', this._boundOnEventFromChild);
    };
	/**
	 * Called on every event emitted on this eventHub, regardles of origin.
	 *
	 * @private
	 *
	 * @param {Mixed}
	 *
	 * @returns {void}
	 */
    lm.utils.EventHub.prototype._onEventFromThis = function () {
        var args = Array.prototype.slice.call(arguments);
        if (this._layoutManager.isSubWindow && args[0] !== this._dontPropagateToParent) {
            this._propagateToParent(args);
        }
        this._propagateToChildren(args);
        //Reset
        this._dontPropagateToParent = null;
        this._childEventSource = null;
    };
	/**
	 * Called by the parent layout.
	 *
	 * @param   {Array} args Event name + arguments
	 *
	 * @returns {void}
	 */
    lm.utils.EventHub.prototype._$onEventFromParent = function (args) {
        this._dontPropagateToParent = args[0];
        this.emit.apply(this, args);
    };
	/**
	 * Callback for child events raised on the window
	 *
	 * @param   {DOMEvent} event
	 * @private
	 *
	 * @returns {void}
	 */
    lm.utils.EventHub.prototype._onEventFromChild = function (event) {
        this._childEventSource = event.originalEvent.__gl;
        this.emit.apply(this, event.originalEvent.__glArgs);
    };
	/**
	 * Propagates the event to the parent by emitting
	 * it on the parent's DOM window
	 *
	 * @param   {Array} args Event name + arguments
	 * @private
	 *
	 * @returns {void}
	 */
    lm.utils.EventHub.prototype._propagateToParent = function (args) {
        var event,
            eventName = 'gl_child_event';
        if (document.createEvent) {
            event = window.opener.document.createEvent('HTMLEvents');
            event.initEvent(eventName, true, true);
        } else {
            event = window.opener.document.createEventObject();
            event.eventType = eventName;
        }
        event.eventName = eventName;
        event.__glArgs = args;
        event.__gl = this._layoutManager;
        if (document.createEvent) {
            window.opener.dispatchEvent(event);
        } else {
            window.opener.fireEvent('on' + event.eventType, event);
        }
    };
	/**
	 * Propagate events to children
	 *
	 * @param   {Array} args Event name + arguments
	 * @private
	 *
	 * @returns {void}
	 */
    lm.utils.EventHub.prototype._propagateToChildren = function (args) {
        var childGl, i;
        for (i = 0; i < this._layoutManager.openPopouts.length; i++) {
            childGl = this._layoutManager.openPopouts[i].getGlInstance();
            if (childGl && childGl !== this._childEventSource) {
                childGl.eventHub._$onEventFromParent(args);
            }
        }
    };
	/**
	 * Destroys the EventHub
	 *
	 * @public
	 * @returns {void}
	 */
    lm.utils.EventHub.prototype.destroy = function () {
        $(window).off('gl_child_event', this._boundOnEventFromChild);
    };
	/**
	 * A specialised GoldenLayout component that binds GoldenLayout container
	 * lifecycle events to react components
	 *
	 * @constructor
	 *
	 * @param {lm.container.ItemContainer} container
	 * @param {Object} state state is not required for react components
	 */
    lm.utils.ReactComponentHandler = function (container, state) {
        this._reactComponent = null;
        this._originalComponentWillUpdate = null;
        this._container = container;
        this._initialState = state;
        this._reactClass = this._getReactClass();
        this._container.on('open', this._render, this);
        this._container.on('destroy', this._destroy, this);
    };
    lm.utils.copy(lm.utils.ReactComponentHandler.prototype, {
		/**
		 * Creates the react class and component and hydrates it with
		 * the initial state - if one is present
		 *
		 * By default, react's getInitialState will be used
		 *
		 * @private
		 * @returns {void}
		 */
        _render: function () {
            this._reactComponent = ReactDOM.render(this._getReactComponent(), this._container.getElement()[0]);
            this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate || function () {
            };
            this._reactComponent.componentWillUpdate = this._onUpdate.bind(this);
            if (this._container.getState()) {
                this._reactComponent.setState(this._container.getState());
            }
        },
		/**
		 * Removes the component from the DOM and thus invokes React's unmount lifecycle
		 *
		 * @private
		 * @returns {void}
		 */
        _destroy: function () {
            ReactDOM.unmountComponentAtNode(this._container.getElement()[0]);
            this._container.off('open', this._render, this);
            this._container.off('destroy', this._destroy, this);
        },
		/**
		 * Hooks into React's state management and applies the componentstate
		 * to GoldenLayout
		 *
		 * @private
		 * @returns {void}
		 */
        _onUpdate: function (nextProps, nextState) {
            this._container.setState(nextState);
            this._originalComponentWillUpdate.call(this._reactComponent, nextProps, nextState);
        },
		/**
		 * Retrieves the react class from GoldenLayout's registry
		 *
		 * @private
		 * @returns {React.Class}
		 */
        _getReactClass: function () {
            var componentName = this._container._config.component;
            var reactClass;
            if (!componentName) {
                throw new Error('No react component name. type: react-component needs a field `component`');
            }
            reactClass = this._container.layoutManager.getComponent(componentName);
            if (!reactClass) {
                throw new Error('React component "' + componentName + '" not found. ' +
                    'Please register all components with GoldenLayout using `registerComponent(name, component)`');
            }
            return reactClass;
        },
		/**
		 * Copies and extends the properties array and returns the React element
		 *
		 * @private
		 * @returns {React.Element}
		 */
        _getReactComponent: function () {
            var defaultProps = {
                glEventHub: this._container.layoutManager.eventHub,
                glContainer: this._container,
            };
            var props = $.extend(defaultProps, this._container._config.props);
            return React.createElement(this._reactClass, props);
        }
    });
})(window.$);