text: lm.items.Stack = function (layoutManager, config, parent) {
  	lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
  
  	this.element = $('<div class="lm_item lm_stack"></div>');
  	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,
  		maximize: cfg.settings.showMaximizeIcon && cfg.labels.maximize,
  		minimize: cfg.settings.showMinimizeIcon && cfg.labels.minimize,
  		close: cfg.settings.showCloseIcon && cfg.labels.close,
  	};
  	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 = $('<div class="lm_items"></div>');
  	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.header.element.height() : 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[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');
  		}
  
  		const oldItem = this._activeContentItem;
  		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);
  		if (oldItem) {
  			oldItem.emit("focusLost");
  		}
  		this.emitBubblingEvent('stateChanged');
  	},
  
  	getActiveContentItem: function () {
  		return this.header.activeContentItem;
  	},
  
  	addChild: function (contentItem, index, activate = true) {
  		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);
  		if ($(this.layoutManager.container).is(":visible") && activate) {
  			this.setActiveContentItem(contentItem);
  		} else {
  			contentItem._$hide();
  		}
  		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);
  		}
  
  		if (this.layoutManager.config.settings.mode !== "workspace") {
  
  			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;
  	}
  });
  