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

/**
 * @fileoverview
 * Implementation of resize gripper for other widgets or HTML elements.
 * @author Partridge Jiang
 */

(function(){

"use strict";

var EU = Kekule.HtmlElementUtils;
var EV = Kekule.X.Event;
var CNS = Kekule.Widget.HtmlClassNames;

/** @ignore */
Kekule.Widget.HtmlClassNames = Object.extend(Kekule.Widget.HtmlClassNames, {
	RESIZEGRIPPER: 'K-Resize-Gripper'
});

var PS = Class.PropertyScope;

/**
 * An gripper widget at the bottom right corner of parent to change the parent's dimension.
 * @class
 * @augments Kekule.Widget.BaseWidget
 *
 * @property {Object} target Target HTML element or widget to be resized.
 * @property {Bool} retainAspectRatio Whether retain width/height ratio when resizing.
 */
Kekule.Widget.ResizeGripper = Class.create(Kekule.Widget.BaseWidget,
/** @lends Kekule.Widget.ResizeGripper# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Widget.ResizeGripper',
	/** @private */
	BINDABLE_TAG_NAMES: ['div', 'span'],
	/** @constructs */
	initialize: function($super, parentOrElementOrDocument)
	{
		this.reactMousemoveBind = this.reactMousemove.bind(this);
		this.reactMouseupBind = this.reactMouseup.bind(this);
		this.reactTouchmoveBind = this.reactTouchmove.bind(this);
		this.reactTouchendBind = this.reactTouchend.bind(this);

		$super(parentOrElementOrDocument);
		if (!this.getTarget())
		{
			if (this.getParent())
				this.setTarget(this.getParent());
			else
			{
				var parentElem = this.getElement().parentNode;
				if (parentElem)
					this.setTarget(parentElem);
			}
		}
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('target', {'dataType': DataType.OBJECT, 'serializable': false,
			'setter': function(value)
			{
				if (this.getTarget() !== value)
				{
					this.setPropStoreFieldValue('target', value);
					this.targetChanged(value);
				}
			}
		});
		this.defineProp('retainAspectRatio', {'dataType': DataType.BOOL});
		// private properties
		this.defineProp('isUnderResizing', {'dataType': DataType.BOOL, 'serializable': false, 'setter': null, 'scope': PS.PRIVATE});
		this.defineProp('baseCoord', {'dataType': DataType.HASH, 'serializable': false, 'scope': PS.PRIVATE});
		//this.defineProp('currCoord', {'dataType': DataType.HASH, 'serializable': false, 'scope': PS.PRIVATE});
		this.defineProp('baseDimension', {'dataType': DataType.HASH, 'serializable': false, 'scope': PS.PRIVATE});
		this.defineProp('baseAspectRatio', {'dataType': DataType.FLOAT, 'serializable': false, 'scope': PS.PRIVATE});
	},

	/** @ignore */
	doGetWidgetClassName: function($super)
	{
		return $super() + ' ' + CNS.RESIZEGRIPPER;
	},
	/** @ignore */
	doCreateRootElement: function(doc)
	{
		var result = doc.createElement('span');
		return result;
	},

	/**
	 * Called when target object is changed.
	 * @param {Object} value
	 * @private
	 */
	targetChanged: function(value)
	{
		var elem;
		// change parent of curr widget
		if (this.targetIsWidget(value))
		{
			elem = value.getResizerElement? value.getResizerElement(): value.getCoreElement();
		}
		else // HTML element
		{
			elem = value;
		}
		if (elem)
			this.appendToElem(elem);
	},
	/**
	 * Check if target object is a widget.
	 * @param {Object} target
	 * @returns {Bool}
	 * @private
	 */
	targetIsWidget: function(target)
	{
		return target instanceof Kekule.Widget.BaseWidget;
	},
	/**
	 * Returns dimension of target object.
	 * @param {Object} obj
	 * @returns {Hash}
	 * @private
	 */
	getTargetDimension: function(obj)
	{
		var target = obj || this.getTarget();
		if (this.targetIsWidget(target))
			return target.getDimension();
		else
			//return Kekule.HtmlElementUtils.getElemBoundingClientRect(target, false);
			return Kekule.HtmlElementUtils.getElemPageRect(target, true);
	},
	/**
	 * Set dimension (in px) of target object.
	 * @param {Object} obj
	 * @param {Int} width
	 * @param {Int} height
	 */
	setTargetDimension: function(obj, width, height)
	{
		var target = obj || this.getTarget();
		if (this.targetIsWidget(target))
			target.setDimension(width, height);
		else  // HTML element
		{
			var style = target.style;
			style.width = width + 'px';
			style.height = height + 'px';
		}
	},

	/**
	 * Prepare to resize.
	 * @private
	 */
	prepareResizing: function(startingCoord, event)
	{
		if (!this.getIsUnderResizing())
		{
			var eventReceiver = this._getMoveEventReceiverElem(this.getElement());
			this._installEventHandlers(eventReceiver);

			this._setMouseCapture(this.getElement(), true, event);

			this.setBaseCoord(startingCoord);
			var dim = this.getTargetDimension();
			this.setBaseDimension(dim);
			this.setBaseAspectRatio(dim.width / dim.height);
			//this.getElement().setCapture(true);
			this.setPropStoreFieldValue('isUnderResizing', true);
			//this.setMouseCapture(true);
		}
	},
	/**
	 * Resizing process is over.
	 */
	doneResizing: function(event)
	{
		this._setMouseCapture(this.getElement(), false, event);

		var eventReceiver = this._getMoveEventReceiverElem(this.getElement());
		this._uninstallEventHandlers(eventReceiver);
		//this.getElement().setCapture(false);
		//this.setMouseCapture(false);
		this.setBaseCoord(null);
		this.setBaseDimension(null);
		this.setPropStoreFieldValue('isUnderResizing', false);
	},
	/**
	 * Resize to a proper size according to starting coord and curr coord.
	 * @param {Hash} currCoord
	 * @private
	 */
	resizeTo: function(currCoord)
	{
		var delta = Kekule.CoordUtils.substract(currCoord, this.getBaseCoord());
		var baseDim = this.getBaseDimension();
		var w = baseDim.width + delta.x;
		var h = baseDim.height + delta.y;
		if (this.getRetainAspectRatio())
		{
			var baseRatio = this.getBaseAspectRatio();
			if (w / h > baseRatio)
				w = baseRatio * h;
			else if (w / h < baseRatio)
				h = w / baseRatio;
		}
		this.setTargetDimension(this.getTarget(), w, h);
	},

	// event reactors
	/** @private */
	reactMousemove: function(e)
	{
		if (this.getIsUnderResizing())
		{
			var coord = {'x': e.getScreenX(), 'y': e.getScreenY()};
			this.resizeTo(coord);
			e.stopPropagation();
			e.preventDefault();
		}
	},
	/** @private */
	reactMouseup: function(e)
	{
		if (e.getButton() === Kekule.X.Event.MouseButton.LEFT)
		{
			if (this.getIsUnderResizing())
			{
				this.doneResizing(e);
				e.preventDefault();
			}
		}
	},
	/** @private */
	reactTouchmove: function(e)
	{
		if (this.getIsUnderResizing())
		{
			var coord = {'x': e.getScreenX(), 'y': e.getScreenY()};
			this.resizeTo(coord);
			e.stopPropagation();
			e.preventDefault();
		}
	},
	/** @private */
	reactTouchend: function(e)
	{
		if (this.getIsUnderResizing())
		{
			this.doneResizing(e);
			e.preventDefault();
		}
	},

	/** @private */
	_elemSupportCapture: function(elem)
	{
		return !!elem.setCapture;
	},
	/** @private */
	_setMouseCapture: function(elem, capture, event)
	{
		if (elem)
		{
			if (capture)
			{
				if (elem.setCapture)
					elem.setCapture(true);
				if (elem.setPointerCapture)
				{
					if (Kekule.ObjUtils.notUnset(event.pointerId))
						elem.setPointerCapture(event.pointerId)
				}
			}
			else
			{
				if (elem.releaseCapture)
					elem.releaseCapture();
				if (elem.releasePointerCapture)
				{
					if (Kekule.ObjUtils.notUnset(event.pointerId))
						elem.releasePointerCapture(event.pointerId)
				}
			}
		}
	},
	/** @private */
	_getMoveEventReceiverElem: function(gripperElem)
	{
		return (this._elemSupportCapture(gripperElem))?
				gripperElem: gripperElem.ownerDocument.documentElement;
	},
	/** @private */
	_installEventHandlers: function(receiver)
	{
		EV.addListener(receiver, 'mousemove', this.reactMousemoveBind);
		EV.addListener(receiver, 'touchmove', this.reactTouchmoveBind);
		EV.addListener(receiver, 'mouseup', this.reactMouseupBind);
		EV.addListener(receiver, 'touchend', this.reactTouchendBind);
	},
	/** @private */
	_uninstallEventHandlers: function(receiver)
	{
		EV.removeListener(receiver, 'mousemove', this.reactMousemoveBind);
		EV.removeListener(receiver, 'touchmove', this.reactTouchmoveBind);
		EV.removeListener(receiver, 'mouseup', this.reactMouseupBind);
		EV.removeListener(receiver, 'touchend', this.reactTouchendBind);
	},

	/** @ignore */
	//react_pointerdown: function(e)
	doReactActiviting: function($super, e)
	{
		$super(e);
		//var evType = e.getType();
		{
			var coord = {'x': e.getScreenX(), 'y': e.getScreenY()};
			this.prepareResizing(coord, e);
		}
	}
	/** @ignore */
	//react_pointerup: function(e)
	/*
	doReactDeactiviting: function($super, e)
	{
		$super(e);
		//if (e.getButton() === Kekule.X.Event.MouseButton.LEFT)
		this.doneResizing();
	},
	*/
	/** @ignore */
	/*
	react_pointermove: function(e)
	{
		if (this.getIsUnderResizing())
		{
			var coord = {'x': e.getScreenX(), 'y': e.getScreenY()};
			this.resizeTo(coord);
			e.stopPropagation();
			e.preventDefault();
		}
	},
	*/
	/** @ignore */
	/*
	react_touchmove: function(e)
	{
		if (this.getIsUnderResizing())
		{
			var coord = {'x': e.getScreenX(), 'y': e.getScreenY()};
			this.resizeTo(coord);
			e.stopPropagation();
			e.preventDefault();
		}
	}
	*/
});

// extend Kekule.Widget.BaseWidget, add resize ability to all widgets
ClassEx.extend(Kekule.Widget.BaseWidget, {
	resizableChanged: function()
	{
		if (this.getResizable())  // add new gripper widget
		{
			var gripper = new Kekule.Widget.ResizeGripper(this);
			gripper.setRetainAspectRatio(this.getResizeWithAspectRatio());
			this.setPropStoreFieldValue('resizeGripper', gripper);
		}
		else
		{
			var gripper = this.getResizeGripper();
			if (gripper)
				gripper.finalize();
			this.setPropStoreFieldValue('resizeGripper', null);
		}
	}
});

ClassEx.defineProps(Kekule.Widget.BaseWidget, [
	{
		'name': 'resizable', 'dataType': DataType.BOOL,
		'setter': function(value)
		{
			if (value !== this.getResizable())
			{
				this.setPropStoreFieldValue('resizable', value);
				this.resizableChanged();
			}
		}
	},
	{
		'name': 'resizeWithAspectRatio', 'dataType': DataType.BOOL,
		'setter': function(value)
		{
			this.setPropStoreFieldValue('resizeWithAspectRatio', value);
			if (this.getResizeGripper())
				this.getResizeGripper().setRetainAspectRatio(value);
		}
	},
	{
		'name': 'isResizing', 'dataType': DataType.BOOL, 'serializable': false, 'scope': PS.PRIVATE,
		'setter': null,
		'getter': function()
		{
			var resizer = this.getResizeGripper();
			return resizer? resizer.getIsUnderResizing(): false;
		}
	},
	{'name': 'resizeGripper', 'dataType': 'Kekule.Widget.ResizeGripper', 'serializable': false, 'setter': null, 'scope': PS.PRIVATE}
]);


})();