Source: widgets/commonCtrls/kekule.widget.containers.js

/**
 * @fileoverview
 * Implementation of container that can hold a set of other widgets.
 * @author Partridge Jiang
 */

/*
 * requires /lan/classes.js
 * requires /utils/kekule.utils.js
 * requires /utils/kekule.domUtils.js
 * requires /xbrowsers/kekule.x.js
 * requires /widgets/kekule.widget.base.js
 */

(function(){

/** @ignore */
Kekule.Widget.HtmlClassNames = Object.extend(Kekule.Widget.HtmlClassNames, {
	CONTAINER: 'K-Container',
	PANEL: 'K-Panel',
	PANEL_CAPTION: 'K-Panel-Caption',
	TOOLBAR: 'K-Toolbar'
});

var DU = Kekule.DomUtils;
var EU = Kekule.HtmlElementUtils;
var CNS = Kekule.Widget.HtmlClassNames;

/**
 * An abstract widget container.
 * @class
 * @augments Kekule.Widget.BaseWidget
 *
 * @property {String} childWidth CSS width of all children. If set to null, width will be determined by child it self.
 * @property {String} childHeight CSS height of all children. If set to null, height will be determined by child it self.
 * @property {String} childMargin CSS margin property of all children.
 * @property {Bool} allowChildWrap Whether child widget can wrap in lines inside parent. Only work in horizontal layout.
 */
Kekule.Widget.Container = Class.create(Kekule.Widget.BaseWidget,
/** @lends Kekule.Widget.Container# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Widget.Container',
	/** @construct */
	initialize: function($super, parentOrElementOrDocument)
	{
	  $super(parentOrElementOrDocument);
		this.reactShowStateChangeBind = this.reactShowStateChange.bind(this);
		this.addEventListener('showStateChange', this.reactShowStateChangeBind);
	},
	/** @private */
	finalize: function($super)
	{
		this.removeEventListener('showStateChange', this.reactShowStateChangeBind);
		this.clearWidgets();
		$super();
	},
	/** @private */
	initProperties: function()
	{
		this.defineChildDimensionRelatedProp('childWidth');
		this.defineChildDimensionRelatedProp('childHeight');
		this.defineChildDimensionRelatedProp('childMargin');

		this.defineProp('firstChild', {'dataType': 'Kekule.Widget.BaseWidget', 'serializable': false, 'setter': null, 'scope': Class.PropertyScope.PUBLIC});
		this.defineProp('lastChild', {'dataType': 'Kekule.Widget.BaseWidget', 'serializable': false, 'setter': null, 'scope': Class.PropertyScope.PUBLIC});
		this.defineProp('allowChildWrap', {'dataType': DataType.BOOL, 'serializable': false,
			'setter': function(value)
			{
				this.setPropStoreFieldValue('allowChildWrap', value);
				if (value)
					this.removeClassName(CNS.NOWRAP);
				else
				{
					this.addClassName(CNS.NOWRAP);
				}
			}
		});
	},
	/** @ignore */
	initPropValues: function($super)
	{
		$super();
		this.setAllowChildWrap(true);
	},

	/** @private */
	defineChildDimensionRelatedProp: function(propName, options)
	{
		var ops = Object.extend({
			'dataType': DataType.STRING,
			'setter': function(value)
			{
				this.setPropStoreFieldValue(propName, value);
				this.updateChildSizes();
			}
		}, options || {});
		return this.defineProp(propName, ops);
	},


	/** @ignore */
	doGetWidgetClassName: function()
	{
		return CNS.CONTAINER;
	},
	/** @ignore */
	doCreateRootElement: function(doc)
	{
		var result = doc.createElement('span');
		return result;
	},
	/**
	 * Returns the parent HTML element to hold all child widgets.
	 * Descendants can override this method.
	 * @return {HTMLElement}
	 */
	getContainerElement: function()
	{
		return this.getElement();
	},

	/**
	 * Append an widget to container/
	 * @param {Kekule.Widget.BaseWidget} widget
	 */
	appendWidget: function(widget)
	{
		/*
		this.getContainerElement().appendChild(widget.getElement());
		*/
		//this._insertChildWidget(widget, null);
		widget.setParent(this);
		return this;
	},
	/**
	 * Insert an widget before refWidget.
	 * @param {Kekule.Widget.BaseWidget} widget
	 * @param {Kekule.Widget.BaseWidget} refWidget
	 */
	insertWidgetBefore: function(widget, refWidget)
	{
		/*
	  var refElem = refWidget? refWidget.getElement(): null;
		if (refElem)
			this.getContainerElement().insertBefore(widget.getElement(), refElem);
		else
			this.getContainerElement().appendChild(widget.getElement());
		*/
		//this._insertChildWidget(widget, refWidget);
		/*
		widget.setParent(this);
		var refIndex = refWidget? this.getChildWidgets().indexOf(refWidget): -1;
		if (refIndex >= 0)
			this._moveChild(widget, refIndex);
		*/
		widget.insertToWidget(this, refWidget);
		return this;
	},

	/**
	 * Remove an widget from container and destroy it.
	 * @param {Kekule.Widget.BaseWidget} widget
	 * @param {Bool} doNotFinalize If set to true, the widget will not be finalized.
	 */
	removeWidget: function(widget, doNotFinalize)
	{
		widget.setParent(null);
		if (!doNotFinalize)
			widget.finalize();
	},

	/**
	 * Remove all children in container and destroy them.
	 * @param {Bool} doNotFinalize If set to true, the widget will not be finalized.
	 */
	clearWidgets: function(doNotFinalize)
	{
		var children = this.getChildWidgets();
		for (var i = children.length - 1; i >= 0; --i)
		{
			var child = children[i];
			this.removeWidget(child, doNotFinalize);
		}
	},

	/** @private */
	_insertChildWidget: function(widget, refWidget)
	{
		var refElem = refWidget? refWidget.getElement(): null;
		var containerElem = this.getContainerElement();
		if (containerElem)
		{
			if (refElem)
				containerElem.insertBefore(widget.getElement(), refElem);
			else
				containerElem.appendChild(widget.getElement());
		}
	},

	/** @private */
	reactShowStateChange: function(e)
	{
		//if (e.target !== this)  // invoked by child widget
		{
			this.childrenModified();
		}
	},

	/** @private */
	childrenModified: function($super)
	{
		$super();
		// change first / last child if essential
		var widgets = this.getChildWidgets();
		var length = widgets.length;

		var index = 0;
		var curr = widgets[index];
		while (curr && (!curr.isShown(true)) && (index < length)) // check show ignoring DOM status
		{
			++index;
			curr = widgets[index];
		}  // get first visible child
		var newFirst = (index < length)? curr: null;

		var index = length - 1;
		var curr = widgets[length - 1];
		while (curr && (!curr.isShown(true)) && (index >= 0)) // check show ignoring DOM status
		{
			--index;
			curr = widgets[index];
		}
		var newLast = (index >= 0)? curr: null;

		var oldFirst = this.getFirstChild();
		var oldLast = this.getLastChild();
		if (newFirst !== oldFirst)
		{
			if (oldFirst)
				oldFirst.removeClassName(CNS.FIRST_CHILD);
			if (newFirst)
				newFirst.addClassName(CNS.FIRST_CHILD);
			this.setPropStoreFieldValue('firstChild', newFirst);
		}
		if (newLast !== oldLast)
		{
			if (oldLast)
				oldLast.removeClassName(CNS.LAST_CHILD);
			if (newLast)
				newLast.addClassName(CNS.LAST_CHILD);
			this.setPropStoreFieldValue('lastChild', newLast);
		}
	},
	/** @private */
	childWidgetAdded: function($super, widget)
	{
		$super(widget);
		var w = this.getChildWidth();
		if (w)
			widget.setWidth(w);
		var h = this.getChildHeight();
		if (h)
			widget.setHeight(h);
		var margin = this.getChildMargin();
		if (margin)
			widget.getStyle().margin = margin;

		this._insertChildWidget(widget, null);
	},
	/** @private */
	childWidgetRemoved: function($super, widget)
	{
		$super(widget);
		//this.getContainerElement().removeChild(widget.getElement());
		// do not need to remove here, this work has been done in _removeChild method of BaseWidget
	},
	/** @private */
	childWidgetMoved: function($super, widget, newIndex)
	{
		$super(widget, newIndex);
		var elem = widget.getElement();
		var refWidget = this.getChildWidgets()[newIndex + 1];
		var refElem = refWidget? refWidget.getElement(): null;
		if (refElem)
			this.getContainerElement().insertBefore(elem, refElem);
		else
			this.getContainerElement().appendChild(elem);
	},

	/**
	 * Change child widgets size according to childWidth/childHeight settings.
	 * @private
	 */
	updateChildSizes: function()
	{
		var children = this.getChildWidgets();
		var w = this.getChildWidth() || '';
		var h = this.getChildHeight() || '';
		var margin = this.getChildMargin() || '';
		var layout = this.getLayout();
		//var marginValue = (layout === Kekule.Widget.Layout.VERTICAL)? margin + ' auto': 'auto ' + margin;
		{
			for (var i = 0, l = children.length; i < l; ++i)
			{
				var widget = children[i];
				widget.setWidth(w);
				widget.setHeight(h);
				widget.getStyle().margin = margin;
			}
		}
	}
});

/**
 * An plain panel to contain child widgets.
 * @class
 * @augments Kekule.Widget.Container
 *
 */
Kekule.Widget.Panel = Class.create(Kekule.Widget.Container,
/** @lends Kekule.Widget.Panel# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Widget.Panel',
	/** @private */
	initProperties: function()
	{
		this.defineProp('caption', {'dataType': DataType.STRING,
			'getter': function() {
				var elem = this.getCaptionElem(false);
				return elem && DU.getElementText(this.getCaptionElem());
			},
			'setter': function(value)
			{
				DU.setElementText(this.getCaptionElem(true), value);
				SU.setDisplay(this.getCaptionElem(), !!value);
			}
		});
		this.defineProp('captionElem', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null,
			'getter': function(canCreate){
				var result = this.getPropStoreFieldValue('captionElem');
				if (!result && canCreate)
				{
					result = this.getDocument().createElement('div');
					result.className = CNS.PANEL_CAPTION;
					// insert at the head of root elem
					var rootElem = this.getElement();
					rootElem.insertBefore(result, DU.getFirstChildElem(rootElem));
					this.setPropStoreFieldValue('captionElem', result);
				}
				return result;
			}
		})
	},
	/** @private */
	initPropValues: function($super)
	{
		$super();
		this.setUseCornerDecoration(true);
	},
	/** @ignore */
	doGetWidgetClassName: function($super)
	{
		return $super() + ' ' + CNS.PANEL;
	}
});

/**
 * An widget group, all child widgets inside it should be regarded as a whole.
 * e.g, button group, edit-button group and so on.
 * @class
 * @augments Kekule.Widget.Container
 *
 */
Kekule.Widget.WidgetGroup = Class.create(Kekule.Widget.Container,
/** @lends Kekule.Widget.WidgetGroup# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Widget.WidgetGroup',
	/** @construct */
	initialize: function($super, parentOrElementOrDocument)
	{
		$super(parentOrElementOrDocument);
	},
	/** @private */
	initPropValues: function($super)
	{
		$super();
		this.setUseCornerDecoration(true);
	},

	/** @ignore */
	doObjectChange: function($super, modifiedPropNames)
	{
		$super(modifiedPropNames);
		if (modifiedPropNames.indexOf('useCornerDecoration') >= 0)
			this._updateChildStyles();
	},

	/** @ignore */
	childWidgetAdded: function($super, widget)
	{
		if (widget.setUseCornerDecoration)
		{
			widget.setUseCornerDecoration(false);
		}
		$super(widget);
	},
	/** @ignore */
	layoutChanged: function($super)
	{
		$super();
		this._updateChildStyles();
	},
	/** @ignore */
	childrenModified: function($super)
	{
		$super();
		this._updateChildStyles();
	},

	/** @private */
	_updateChildStyles: function()
	{
		var WL = Kekule.Widget.Layout;
		var layout = this.getLayout();
		var first = this.getFirstChild();
		var last = this.getLastChild();
		var useCorner = /*true; //*/ this.getUseCornerDecoration();
		var children = this.getChildWidgets();
		/*
		 var allFirstRoundClasses = [CNS.CORNER_LEFT, CNS.CORNER_TOP];
		 var firstRoundClass = (layout === WL.VERTICAL)? CNS.CORNER_TOP: CNS.CORNER_LEFT;
		 var allLastRoundClasses = [CNS.CORNER_RIGHT, CNS.CORNER_BOTTOM];
		 var lastRoundClass = (layout === WL.VERTICAL)? CNS.CORNER_BOTTOM: CNS.CORNER_RIGHT;
		 */
		var allRoundClasses = [CNS.CORNER_LEADING, CNS.CORNER_TAILING];

		// TODO: Now has to iterate all children, too slow, need to change later.
		for (var i = 0, l = children.length; i < l; ++i)
		{
			var child = children[i];
			child.removeClassName(allRoundClasses);
			if (child === first)
			{
				if (useCorner)
					child.addClassName(CNS.CORNER_LEADING);
			}
			if (child === last)
			{
				if (useCorner)
					child.addClassName(CNS.CORNER_TAILING);
			}
		}
		//console.log('update child style', children.length, useCorner, first, last);
	}
});

/**
 * An general toolbar that can contain child widgets.
 * @class
 * @augments Kekule.Widget.WidgetGroup
 *
 * @property {Array} childDefs Array of hash definition of child widgets.
 *   In definition, a special field "internalName" can be set. After created, the
 *   child widget can be refered by {@link Kekule.Widget.Toolbar.getChildWidgetByInternalName} method.
 *   When this property is set, new child widgets will be created by it and all old child widgets will be destroyed.
 */
Kekule.Widget.Toolbar = Class.create(Kekule.Widget.WidgetGroup,
/** @lends Kekule.Widget.Toolbar# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Widget.Toolbar',
	/** @ignore */
	finalize: function($super)
	{
		var map = this.getChildWidgetInternalNameMap();
		if (map)
			map.finalize();
		$super();
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('childDefs', {
			'dataType': DataType.ARRAY,
			'setter': function(value)
			{
				this.setPropStoreFieldValue('childDefs', value);
				this.recreateChildrenByDefs();
			}
		});
		// private
		this.defineProp('childWidgetInternalNameMap', {'dataType': DataType.OBJECT, 'serializable': false});
	},
	/** @ignore */
	doGetWidgetClassName: function($super)
	{
		return $super() + ' ' + CNS.TOOLBAR;
	},
	/** @private */
	doSetShowText: function($super, value)
	{
		$super(value);
		this._updateAllChildTextGlyphStyles();
	},
	doSetShowGlyph: function($super, value)
	{
		$super(value);
		this._updateAllChildTextGlyphStyles();
	},

	/** @private */
	childWidgetAdded: function($super, widget)
	{
		$super(widget);
		this._updateChildTextGlyphStyles(widget);
	},

	/** @private */
	_updateChildTextGlyphStyles: function(widget)
	{
		if (widget.setShowText)
			widget.setShowText(this.getShowText());
		if (widget.setShowGlyph)
			widget.setShowGlyph(this.getShowGlyph());
	},
	/** @private */
	_updateAllChildTextGlyphStyles: function()
	{
		var children = this.getChildWidgets();
		for (var i = children.length - 1; i >= 0; --i)
		{
			this._updateChildTextGlyphStyles(children[i]);
		}
	},

	/** @private */
	recreateChildrenByDefs: function()
	{
		var defs = this.getChildDefs() || [];
		// remove old children first
		this.clearWidgets();
		var internalNameMap = new Kekule.MapEx(true);
		this.setChildWidgetInternalNameMap(internalNameMap);
		// add new ones
		var defWidgetClassName = this.getDefaultChildWidgetClassName();
		for (var i = 0, l = defs.length; i < l; ++i)
		{
			var def = defs[i];
			if (!def.widget && !def.widgetClass && defWidgetClassName)  // class not set, try to use default one
			{
				def = Object.extend({'widget': defWidgetClassName}, def);
			}
			var w = Kekule.Widget.createFromHash(this, def);
			if (w)
			{
				w.appendToWidget(this);
				if (def.internalName)
				{
					internalNameMap.set(def.internalName, w);
				}
			}
		}
	},
	/**
	 * Returns default class name of child widget.
	 * This method is used in create child widgets by hash definition.
	 * Descendants may override this method.
	 * @returns {String}
	 */
	getDefaultChildWidgetClassName: function()
	{
		return null;
	},

	/**
	 * Returns child widget defined by internalName.
	 * @param {String} name
	 * @returns {Kekule.Widget.BaseWidget}
	 */
	getChildWidgetByInternalName: function(name)
	{
		var map = this.getChildWidgetInternalNameMap();
		return map? map.get(name): null;
	}
});

})();