/**
* @fileoverview
* Implementation of different HTML transitions.
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* requires /core/kekule.common.js
* requires /utils/kekule.utils.js
* requires /utils/kekule.domUtils.js
* requires /xbrowsers/kekule.x.js
*/
(function(){
/** @ignore */
var D = Kekule.Widget.Direction;
/** @ignore */
SU = Kekule.StyleUtils;
/**
* Class to execute transition on HTML elements.
* This is the base (abstract class), concrete job need to be done in descendants.
* @class
* @arguments {ObjectEx}
*
* @property {HTMLElement} caller Who calls this transition. Usually a base HTML element of a widget.
* @property {HTMLElement} element The element to run the transition.
*/
Kekule.Widget.BaseTransition = Class.create(ObjectEx,
/** @lends Kekule.Widget.BaseTransition# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.BaseTransition',
/** @constructs */
initialize: function($super)
{
$super();
},
/** @private */
initProperties: function()
{
this.defineProp('element', {'dataType': DataType.OBJECT, 'serializable': false});
this.defineProp('caller', {'dataType': DataType.OBJECT, 'serializable': false});
//this.defineProp('currParams', {'dataType': DataType.HASH, 'serializable': false}); // private
},
/**
* Return if transition can be executed in current browser and element.
* @param {HTMLElement} element
* @param {Hash} options Transition options.
*/
canExecute: function(element, options)
{
return true;
},
/**
* Prepare before run the execution.
* In most cases, information (position, color and so on) need to be saved before execution.
* Descendants need to override this method to save their own info.
* @param {HTMLElement} element
* @param {HTMLElement} caller
* @param {Hash} options
* @private
*/
prepare: function(element, caller, options)
{
//return this.doPrepare(element, options);
// do nothing here
},
/**
* Called after the transition is done.
* Usually some properties of element (position, color and so on) need to be restored after execution.
* Descendants need to override this method to do their own job.
* @param {HTMLElement} element
* @param {HTMLElement} caller
* @param {Hash} options
*/
finish: function(element, caller, options)
{
// do nothing here
},
/**
* Execute transition on element, from and to is the position of the begin and end of transition.
* Those position values are in 0..1. In appear/disappear transition, 0 means invisible and 1 means totally visible.
* @param {HTMLElement} element
* @param {HTMLElement} caller
* @param {Function} callback Function that will be called when the transition is done. This function has no parameters.
* @param {Hash} options Transition options, can include the following fields:
* {
* from: Float, the starting position of transition. For appear/disappear transition, 0 means hidden and 1 means totally shown.
* to: Float, the ending position of transition. For appear/disappear transition, 0 means hidden and 1 means totally shown.
* duration: Int, in millisecond, the duration of transition.
* callerRect: {left, top, width, height}, explicitly set the page rect of caller
* ...
* }
* Different transition may has more properties here.
* @returns {Object} Transition info, an object that contains the basic information of transition, including: {element, caller, callback, options, executor(this)}.
*/
execute: function(element, caller, callback, options)
{
this.setElement(element);
if (caller)
this.setCaller(caller);
var ops = Object.extend({
'from': 0,
'to': 1
}, options);
var self = this;
var done = function()
{
if (done.__$applied__) // avoid duplicated call
return;
done.__$applied__ = true;
self.finish(element, caller, ops);
if (callback)
callback();
};
done.__$applied__ = false;
this.prepare(element, caller, ops);
var self = this;
var result = {
'element': element,
'caller': caller,
'callback': callback,
'doneCallback': done,
'options': options,
'executor': this,
'halt': function() // a helper function to call halt by transition info easily
{
self.halt(result);
}
};
this.doExecute(element, caller, done, ops);
return result;
},
/**
* Do actual job of execute. Descendants should override this method.
* @param {HTMLElement} element
* @param {HTMLElement} caller
* @param {Function} callback Function that will be called when the transition is done. This function has no parameters.
* @param {Hash} options Transition options.
*/
doExecute: function(element, caller, callback, options)
{
// do nothing here
},
/**
* Stop and jump to the end of a executing transition.
* @param {Object} transitionInfo Transition info object returned by {@link Kekule.Widget.BaseTransition.execute}.
*/
halt: function(transitionInfo)
{
if (transitionInfo.executor !== this)
transitionInfo.executor.halt(transitionInfo);
else
{
this.doHalt(transitionInfo);
if (transitionInfo.doneCallback)
{
transitionInfo.doneCallback();
}
}
},
/**
* Do actual work of halt, descendants should override this method.
* @param {Object} transitionInfo
*/
doHalt: function(transitionInfo)
{
// do nothing here
},
/**
* Prepare HTML element before transition executing. Set property properly according to transition position.
* Descendants should override this method.
* @param {HTMLElement} element
* @param {Float} position
* @param {Hash} options
*/
setElementProp: function(element, position, options)
{
// do nothing here
}
});
/**
* Abstract executor based on CSS3 transition.
* @class
* @arguments {Kekule.Widget.BaseTransition}
*
* @property {String} cssProperty CSS3 transition-property.
* Can use comma to separate multiple properties, e.g. 'width, height'.
* @property {String} timingFunc CSS3 transition-timing-function.
*/
Kekule.Widget.Css3Transition = Class.create(Kekule.Widget.BaseTransition,
/** @lends Kekule.Widget.Css3Transition# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.Css3Transition',
/** @constructs */
initialize: function($super)
{
$super();
},
/** @private */
initProperties: function()
{
this.defineProp('cssProperty', {'dataType': DataType.STRING});
this.defineProp('timingFunc', {'dataType': DataType.STRING});
},
/**
* Returns an array of CSS property names that may be changed during transition.
* Descendants may override this method.
* @param {Hash} transOptions
* @returns {Array}
* @private
*/
getAffectedCssPropNames: function(transOptions)
{
// do nothing here
},
/**
* Determinate the direct CSS transition property names.
* @param {Hash} transOptions
* @returns {Array} Properties to be changed.
* @private
*/
getTransCssPropNames: function(transOptions)
{
// do nothing here
},
/**
* Store CSS element inline style into a JavaScript object.
* @param {HTMLElement} element
* @param {Array} propNames Stored property names
* @returns {Hash}
* @private
*/
storeCssInlineValues: function(element, propNames)
{
var style = element.style;
var result = {};
//var names = Kekule.ObjUtils.getOwnedFieldNames(props);
for (var i = 0, l = propNames.length; i < l; ++i)
{
var name = propNames[i];
result[name] = style[name];
}
return result;
},
/**
* Restore element CSS inline styles from a JavaScript hash object.
* @param {HTMLElement} element
* @param {Array} propNames
* @param {Hash} storage
* @private
*/
restoreCssInlineValues: function(element, propNames, storage)
{
var style = element.style;
for (var i = 0, l = propNames.length; i < l; ++i)
{
var name = propNames[i];
if (!storage[name])
{
try
{
style.removeProperty(name);
}
catch(e)
{
style[name] = '';
}
}
else
style[name] = storage[name];
}
},
/** @private */
storeCssComputedValues: function(element, propNames)
{
var result = {};
for (var i = 0, l = propNames.length; i < l; ++i)
{
var name = propNames[i];
var s = SU.getComputedStyle(element, name);
result[name] = s; //SU.analysisUnitsValue(s);
}
return result;
},
/** @ignore */
canExecute: function(element, options)
{
return Kekule.BrowserFeature.cssTransition;
},
/**
* Check if CSS3 transition is supported by current browser.
* @returns {Bool}
*/
isSupported: function()
{
return Kekule.BrowserFeature.cssTransition;
},
/**
* Check if option means an appear transition.
* @param {Hash} options
* @returns {Bool}
* @private
*/
isAppear: function(options)
{
return !!options.isAppear;
},
/**
* Check if option means an disappear transition.
* @param {Hash} options
* @returns {Bool}
* @private
*/
isDisappear: function(options)
{
return !!options.isDisappear;
},
/** @private */
setTransitionProp: function(styleObj, propName, value)
{
//console.log(propName, value);
// help to set CSS properties with -moz- or -webkit prefix
var propNames = [
'Moz' + propName.capitalizeFirst(),
'Webkit' + propName.capitalizeFirst(),
propName
];
for (var i = 0, l = propNames.length; i < l; ++i)
{
styleObj[propNames[i]] = value;
}
},
/** @private */
getOriginCssInlineValues: function()
{
return this._originCssInlineValues;
},
/** @private */
getComputedCssValues: function()
{
return this._computedCssValues;
},
/** @private */
prepare: function(element, caller, options)
{
//console.log('prepare', this.getTransCssPropNames());
this.setCssProperty(this.getTransCssPropNames(options).join(','));
var cssPropNames = this.getAffectedCssPropNames(options);
this._affectedCssPropNames = cssPropNames;
// store CSS values to future use
this._originCssInlineValues = this.storeCssInlineValues(element, cssPropNames);
this._computedCssValues = this.storeCssComputedValues(element, cssPropNames);
},
/** @private */
finish: function(element, caller, options)
{
if ((options.to === 1) || this.isAppear(options) || this.isDisappear(options))
{
//console.log('finish transition', this._affectedCssPropNames, this._originCssInlineValues);
this.restoreCssInlineValues(element, this._affectedCssPropNames, this._originCssInlineValues);
}
},
/** @private */
doExecute: function(element, caller, callback, options)
{
//console.log('transition execute', options);
var SU = Kekule.StyleUtils;
var style = element.style;
var isAppear = this.isAppear(options);
var isDisappear = this.isDisappear(options);
if (this.isSupported())
{
this.setElementProp(element, options.from, options);
if (!SU.isDisplayed(element))
SU.setDisplay(element, true);
if (!SU.isVisible(element))
SU.setVisibility(element, true);
// set transition initial CSS properties
var s = this.getCssProperty();
this.setTransitionProp(style, 'transitionProperty', s);
s = this.getTimingFunc();
if (s)
{
this.setTransitionProp(style, 'transitionTimingFunction', s);
}
s = options.duration.toString() + 'ms';
this.setTransitionProp(style, 'transitionDuration', s);
/*
// check if there is a undismissed event handler.
// This situation may occur when a execution process is request before prev execution finished
if (this._currEventHandler)
Kekule.X.Event.removeListener(element, 'transitionend', this._currEventHandler);
*/
// TODO: Need find a better solution to avoid transition overlap on one element
// install transitionend event handler
var fallbackTimeout;
var self = this;
var done = function(e)
{
//console.log('transition done', callback);
Kekule.X.Event.removeListener(element, 'transitionend', done);
// clear transition props
self.setTransitionProp(style, 'transition', '');
// clear fallback
if (fallbackTimeout)
clearTimeout(fallbackTimeout);
if (callback)
callback();
};
this._currEventHandler = done;
Kekule.X.Event.addListener(element, 'transitionend', done);
// some times the transitionend event will not be evoked
// (e.g., transition on element out of document in Chrome)
// so we need a fallback
fallbackTimeout = setTimeout(done, options.duration + 100);
//console.log('transition execute');
// set "to" property, need use setTimeOut to force browser update DOM
setTimeout(function(){
self.setElementProp(element, options.to, options);
}, 0);
}
else // not supported
{
try
{
this.setElementProp(element, options.to, options);
}
catch(e) // if error occurs, anyway, we will proceed
{
}
/*
if (callback)
callback();
*/
if (callback) // delay call callback, after all routines of execute
setTimeout(callback, 0);
}
},
/** @private */
doHalt: function(transitionInfo)
{
var elem = transitionInfo.element;
if (elem)
{
//console.log('===================');
//setTimeout(this.setTransitionProp/*.bind(this)*/, 0, elem.style, 'transitionProperty', 'none');
this.setTransitionProp(elem.style, 'transitionProperty', 'none');
}
}
});
/* @ignore */
/*
Kekule.Widget.Css3SlideDownTransExecutor = TCC.create('Kekule.Widget.Css3SlideDownTransExecutor', 'height', null, {
canExecute: function($super, element, options)
{
return ($super(element, options) &&
Kekule.ObjUtils.notUnset(SU.analysisUnitsValue(SU.getComputedStyle(element, 'height')).value));
},
prepare: function(element, options)
{
SU.setDisplay(element, true); // important, otherwise width info could not be get.
var s = SU.getComputedStyle(element, 'height');
this._elemHeightInfo = SU.analysisUnitsValue(s);
this._originHeight = element.style.height;
this._originOverflow = element.style.overflow;
element.style.overflow = 'hidden';
},
finish: function(element, options)
{
if ((options.to === 1) || this.isAppear(options) || this.isDisappear(options))
{
if (!this._originWidth)
element.style.removeProperty('height');
else
element.style.height = this._originHeight;
if (this._originOverflow)
element.style.overflow = this._originOveflow;
else
element.style.removeProperty('overflow');
}
},
setElementProp: function(element, position, options)
{
var s = this._elemHeightInfo.value * position + this._elemHeightInfo.units;
element.style.height = s;
}
});
*/
/* @ignore */
/*
Kekule.Widget.Css3SlideRightTransExecutor = TCC.create('Kekule.Widget.Css3SlideRightTransExecutor', 'width', null, {
canExecute: function($super, element, options)
{
return ($super(element, options) &&
Kekule.ObjUtils.notUnset(SU.analysisUnitsValue(SU.getComputedStyle(element, 'width')).value));
},
prepare: function(element, options)
{
SU.setDisplay(element, true); // important, otherwise width info could not be get.
var s = SU.getComputedStyle(element, 'width');
this._elemWidthInfo = SU.analysisUnitsValue(s);
this._originWidth = element.style.width;
this._originOverflow = element.style.overflow || '';
element.style.overflow = 'hidden';
},
finish: function(element, options)
{
if ((options.to === 1) || this.isAppear(options) || this.isDisappear(options))
{
if (!this._originWidth)
element.style.removeProperty('width');
else
element.style.width = this._originWidth;
if (this._originOverflow)
element.style.overflow = this._originOveflow;
else
element.style.removeProperty('overflow');
}
},
setElementProp: function(element, position, options)
{
var s = this._elemWidthInfo.value * position + this._elemWidthInfo.units;
element.style.width = s;
}
});
*/
/**
* Opacity transition executor based on CSS3 transition.
* @class
* @arguments {Kekule.Widget.Css3Transition}
*
* @property {Int} direction The direction of slide transition.
*/
Kekule.Widget.Css3OpacityTrans = Class.create(Kekule.Widget.Css3Transition,
/** @lends Kekule.Widget.Css3OpacityTrans# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.Css3OpacityTrans',
/** @private */
canExecute: function($super, element, options)
{
return !!Kekule.BrowserFeature.cssTranform;
},
/** @private */
getAffectedCssPropNames: function(transOptions)
{
return ['opacity'];
},
/** @private */
getTransCssPropNames: function(transOptions)
{
return ['opacity'];
},
/** @private */
setElementProp: function(element, position, options)
{
//console.log(this._computedCssValues);
var originOpacity = parseFloat(this._computedCssValues.opacity) || 1;
element.style.opacity = originOpacity * position;
}
});
/**
* Slide transition executor based on CSS3 transition.
* @class
* @arguments {Kekule.Widget.Css3Transition}
*
* @property {Int} direction The direction of slide transition.
*/
Kekule.Widget.Css3SlideTransition = Class.create(Kekule.Widget.Css3Transition,
/** @lends Kekule.Widget.Css3SlideTransition# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.Css3SlideTransition',
/** @constructs */
initialize: function($super, direction)
{
$super();
if (Kekule.ObjUtils.notUnset(direction))
this.setDirection(direction);
},
/** @private */
initProperties: function()
{
this.defineProp('direction', {'dataType': DataType.INT});
},
/** @private */
canExecute: function($super, element, options)
{
return true;
/*
var result = $super(element, options);
if (result)
{
var direction = this.getDirection();
if (D.isInHorizontal(direction))
result = Kekule.ObjUtils.notUnset(SU.analysisUnitsValue(SU.getComputedStyle(element, 'width')).value);
if (D.isInVertical(direction))
result = result && Kekule.ObjUtils.notUnset(SU.analysisUnitsValue(SU.getComputedStyle(element, 'height')).value);
}
return result;
*/
},
/** @private */
prepare: function($super, element, caller, options)
{
//console.log('prepare', options);
SU.setDisplay(element, true); // important, otherwise width/height info could not be get.
this._direction = this.getActualDirection(element, options); // save this value, avoid user change direction property during transition
$super(element, caller, options);
/*
var direction = this.getDirection();
this._direction = direction; // save this value, avoid user change direction property during transition
var props = this.getCssTransPropNames(this.getDirection());
this.setCssProperty(props.join(','));
var storageCssPropNames = [];
*/
//var storageCompStyleNames = [];
/*
if (D.isInHorizontal(direction))
{
storageCssPropNames.push('width');
if (direction & D.RTL)
storageCssPropNames.push('left');
}
if (D.isInVertical(direction))
{
storageCssPropNames.push('height');
if (direction & D.BTT)
storageCssPropNames.push('top');
}
storageCssPropNames.push('overflow');
this._storageCssPropNames = storageCssPropNames;
this._originCssStorage = this.storeCssInlineValues(element, storageCssPropNames);
this._computedCssStorage = this.storeCssComputedValues(element, storageCssPropNames);
*/
//console.log(direction, storageCssPropNames);
//console.log(this._originCssStorage);
//console.log(this._computedCssStorage);
element.style.overflow = 'hidden';
this._clearDimesionConstraints(element);
},
/** @private */
_clearDimesionConstraints: function(elem)
{
var style = elem.style;
style.minWidth = '0';
style.minHeight = '0';
style.maxWidth = 'none';
style.maxHeight = 'none';
},
/** @private */
setElementProp: function(element, position, options)
{
var direction = this._direction;
var s;
if (D.isInHorizontal(direction))
{
s = this.getComputedCssValues().width;
var widthInfo = SU.analysisUnitsValue(s);
element.style.width = widthInfo.value * position + widthInfo.units;
s = this.getComputedCssValues().left;
if (s)
{
var leftInfo = SU.analysisUnitsValue(s);
if (leftInfo.units === widthInfo.units)
{
element.style.left = (widthInfo.value * (1 - position) + leftInfo.value) + leftInfo.units;
}
}
}
if (D.isInVertical(direction))
{
s = this.getComputedCssValues().height;
var heightInfo = SU.analysisUnitsValue(s);
element.style.height = heightInfo.value * position + heightInfo.units;
s = this.getComputedCssValues().top;
if (s)
{
var topInfo = SU.analysisUnitsValue(s);
if (topInfo.units === heightInfo.units)
element.style.top = (heightInfo.value * (1 - position) + topInfo.value) + topInfo.units;
}
}
},
/** @private */
getAffectedCssPropNames: function(transOptions)
{
var result = [].concat(this.getTransCssPropNames(transOptions));
result = result.concat(['overflow', 'min-width', 'min-height', 'max-width', 'max-height']);
return result;
},
/**
* Determinate the CSS properties to be changed in transition.
* @returns {Array} Properties to be changed.
* @private
*/
getTransCssPropNames: function(transOptions)
{
var direction = this.getActualDirection(this.getElement(), transOptions);
var result = [];
if (D.isInHorizontal(direction))
{
result.push('width');
if (direction & D.RTL)
result.push('left');
}
if (D.isInVertical(direction))
{
result.push('height');
if (direction & D.BTT)
result.push('top');
}
return result;
},
/** @private */
getRefRect: function(transOptions)
{
var result;
var EU = Kekule.HtmlElementUtils;
if (this.getCaller())
{
/*
var pos = EU.getElemPagePos(this.getCaller());
var dim = EU.getElemClientDimension(this.getCaller());
return {
'x': pos.x || 0,
'y': pos.y || 0,
'width': dim.width || 0,
'height': dim.height || 0
};
*/
result = EU.getElemPageRect(this.getCaller());
//result = EU.getElemBoundingClientRect(this.getCaller(), true);
if (Kekule.RectUtils.isZero(result)) // rect is null, maybe the caller widget is hidden, use cached one instead
{
result = (transOptions || {}).callerPageRect || null;
}
}
else
{
result = null;
}
return result;
},
getActualDirection: function(element, transOptions)
{
if (!element)
element = this.getElement();
var D = Kekule.Widget.Direction;
var EU = Kekule.HtmlElementUtils;
var result = this.getDirection();
if (!result || (result === D.AUTO))
{
var refRect = this.getRefRect(transOptions);
//console.log(refRect);
if (!refRect)
result = D.TTB; // default
else
{
var refCenter = {'x': refRect.x + refRect.width / 2, 'y': refRect.y + refRect.height / 2};
var selfPos = EU.getElemPagePos(element);
var selfDim = EU.getElemClientDimension(element);
var selfCenter = {'x': selfPos.x + (selfDim.width || 0) / 2, 'y': selfPos.y + (selfDim.height || 0) / 2};
var delta = Kekule.CoordUtils.substract(selfCenter, refCenter);
if (refRect.width < 1) // very slim refRect
{
result = (delta.x >= 0)? D.LTR: D.RTL;
}
else if (delta.x < 1) // very close to horizontal line
{
result = (delta.y >= 0)? D.TTB: D.BTT;
}
else
{
var r1 = Math.abs(refRect.height / refRect.width);
var r2 = Math.abs(delta.y / delta.x);
if (r1 > r2) // left or right side
{
result = (delta.x >= 0)? D.LTR: D.RTL;
}
else // top or bottom side
{
result = (delta.y >= 0)? D.TTB: D.BTT;
}
}
}
}
return result;
}
});
/**
* Grow/shrink transition executor based on CSS3 transition.
* @class
* @arguments {Kekule.Widget.Css3Transition}
*
* @property {Hash} baseRect. The starting rectangle to grow or the ending rectangle to shrink.
* Note the x/y value is relative to top-left corner of HTML page.
* If this property is not set, rect calculated from caller will be used.
* //@property {HTMLElement} baseElement The baseRect can be calculated from this element.
*/
Kekule.Widget.Css3GrowTransition = Class.create(Kekule.Widget.Css3Transition,
/** @lends Kekule.Widget.Css3GrowTransition# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.Css3GrowTransition',
/** @constructs */
initialize: function($super, baseRectOrCaller)
{
$super();
if (baseRectOrCaller)
{
if (baseRectOrCaller.ownerDocument) // is element
{
this.setBaseElement(baseRectOrCaller);
}
else
{
this.setCaller(baseRectOrCaller);
}
}
},
/** @private */
initProperties: function()
{
this.defineProp('baseRect', {'dataType': DataType.HASH});
//this.defineProp('baseElement', {'dataType': DataType.OBJECT, 'serializable': false});
},
/** @private */
getRefRect: function(transOptions)
{
var EU = Kekule.HtmlElementUtils;
var result = null;
if (this.getBaseRect())
result = this.getBaseRect();
else if (this.getCaller())
{
/*
var pos = EU.getElemPagePos(this.getCaller());
var dim = EU.getElemClientDimension(this.getCaller());
return {
'x': pos.x,
'y': pos.y,
'width': dim.width,
'height': dim.height
};
*/
//result = EU.getElemPageRect(this.getCaller());
result = EU.getElemBoundingClientRect(this.getCaller(), true);
if (Kekule.RectUtils.isZero(result))
result = (transOptions || {}).callerPageRect || null;
}
if (!result)
{
result = {'x': 0, 'y': 0, 'width': 0, 'height': 0};
}
return result;
},
/** @private */
canExecute: function($super, element, options)
{
return true;
/*
var result = $super(element, options);
if (result)
{
result = Kekule.ObjUtils.notUnset(SU.analysisUnitsValue(SU.getComputedStyle(element, 'width')).value);
result = result && Kekule.ObjUtils.notUnset(SU.analysisUnitsValue(SU.getComputedStyle(element, 'height')).value);
}
return result;
*/
},
/** @private */
prepare: function($super, element, caller, options)
{
SU.setDisplay(element, true); // important, otherwise width/height info could not be get.
$super(element, caller, options);
element.style.overflow = 'hidden';
Kekule.HtmlElementUtils.makePositioned(element);
var refRect = this.getRefRect(options);
// calculate delta information
var EU = Kekule.HtmlElementUtils;
/*
var opos = EU.getElemPagePos(element);
var odim = EU.getElemClientDimension(element);
var delta = {
x: opos.x - refRect.x,
y: opos.y - refRect.y,
width: odim.width - refRect.width,
height: odim.height - refRect.height
};
*/
var odim = EU.getElemBoundingClientRect(element, true);
var delta = {
x: odim.x - refRect.x,
y: odim.y - refRect.y,
width: odim.width - refRect.width,
height: odim.height - refRect.height
};
this._delta = delta;
this._clearDimesionConstraints(element);
},
/** @private */
_clearDimesionConstraints: function(elem)
{
var style = elem.style;
style.minWidth = '0';
style.minHeight = '0';
style.maxWidth = 'none';
style.maxHeight = 'none';
},
/** @private */
setElementProp: function(element, position, options)
{
var compStyles = this.getComputedCssValues();
var delta = this._delta;
var ratio = 1 - position;
//var curr = {};
var style = element.style;
//console.log(compStyles, delta);
var info = SU.analysisUnitsValue(compStyles.left);
if (info.units === 'px')
style.left = (info.value - ratio * delta.x) + 'px';
var info = SU.analysisUnitsValue(compStyles.top);
if (info.units === 'px')
style.top = (info.value - ratio * delta.y) + 'px';
var info = SU.analysisUnitsValue(compStyles.width);
if (info.units === 'px')
style.width = (info.value - ratio * delta.width) + 'px';
var info = SU.analysisUnitsValue(compStyles.height);
if (info.units === 'px')
style.height = (info.value - ratio * delta.height) + 'px';
},
/** @private */
getAffectedCssPropNames: function(transOptions)
{
var result = [].concat(this.getTransCssPropNames(transOptions));
// TODO: min/max/width/height should be taken into consideration
result = result.concat(['overflow', 'min-width', 'min-height', 'max-width', 'max-height']);
return result;
},
/**
* Determinate the CSS properties to be changed in transition.
* @returns {Array} Properties to be changed.
* @private
*/
getTransCssPropNames: function(transOptions)
{
return ['left', 'top', 'width', 'height', 'position'];
}
});
if (Kekule.BrowserFeature && Kekule.BrowserFeature.cssTranform)
{
/**
* Base transform transition executor based on CSS3 transition.
* This is a base class, so do not use it directly.
* @class
* @arguments {Kekule.Widget.Css3Transition}
*
* @property {Int} direction The direction of slide transition.
*/
Kekule.Widget.Css3TransformTrans = Class.create(Kekule.Widget.Css3Transition,
/** @lends Kekule.Widget.Css3TransformTrans# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.Css3TransformTrans',
/** @private */
getAffectedCssPropNames: function(transOptions)
{
return ['transform', 'transformOrigin'];
},
/** @private */
getTransCssPropNames: function(transOptions)
{
return ['transform', 'transformOrigin'];
},
/** @ignore */
prepare: function($super, element, caller, options)
{
$super(element, caller, options);
var computedTransMatrixInfo = Kekule.StyleUtils.getTransformMatrixValues(element);
if (computedTransMatrixInfo)
{
computedTransMatrixInfo.scaleX = computedTransMatrixInfo.a;
computedTransMatrixInfo.scaleY = computedTransMatrixInfo.d;
}
this._computedTransMatrixInfo = computedTransMatrixInfo;
},
/** @private */
setElemTransform: function(elem, tx, ty, sx, sy)
{
var values = [sx, 0, 0, sy, tx, ty];
Kekule.StyleUtils.setTransformMatrixArrayValues(elem, values);
}
});
/**
* Grow/shrink transition executor based on CSS3 transform transition.
* @class
* @arguments {Kekule.Widget.Css3TransformTrans}
*
* @property {Hash} baseRect. The starting rectangle to grow or the ending rectangle to shrink.
* Note the x/y value is relative to top-left corner of HTML page.
* If this property is not set, rect calculated from caller will be used.
* //@property {HTMLElement} baseElement The baseRect can be calculated from this element.
*/
Kekule.Widget.Css3TransformGrowTransition = Class.create(Kekule.Widget.Css3TransformTrans,
/** @lends Kekule.Widget.Css3TransformGrowTransition# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.Css3TransformGrowTransition',
/** @constructs */
initialize: function($super, baseRectOrCaller)
{
$super();
if (baseRectOrCaller)
{
if (baseRectOrCaller.ownerDocument) // is element
{
this.setBaseElement(baseRectOrCaller);
}
else
{
this.setCaller(baseRectOrCaller);
}
}
},
/** @private */
initProperties: function()
{
this.defineProp('baseRect', {'dataType': DataType.HASH});
//this.defineProp('baseElement', {'dataType': DataType.OBJECT, 'serializable': false});
},
/** @private */
getRefRect: function(transOptions)
{
var EU = Kekule.HtmlElementUtils;
var result = null;
if (this.getBaseRect())
result = this.getBaseRect();
else if (this.getCaller())
{
//result = EU.getElemPageRect(this.getCaller());
result = EU.getElemBoundingClientRect(this.getCaller(), true);
if (Kekule.RectUtils.isZero(result))
result = (transOptions || {}).callerPageRect || null;
}
if (!result)
{
result = {'x': 0, 'y': 0, 'width': 0, 'height': 0};
}
return result;
},
/** @private */
prepare: function($super, element, caller, options)
{
SU.setDisplay(element, true); // important, otherwise width/height info could not be get.
$super(element, caller, options);
element.style.overflow = 'hidden';
Kekule.HtmlElementUtils.makePositioned(element);
var refRect = this.getRefRect(options);
// calculate delta information
var EU = Kekule.HtmlElementUtils;
//var opos = EU.getElemPagePos(element);
//var odim = EU.getElemClientDimension(element);
var odim = EU.getElemBoundingClientRect(element, true);
//var odim = EU.getElemPageRect(element);
var transDelta = {
x: odim.x - refRect.x,
y: odim.y - refRect.y
//width: odim.width - refRect.width,
//height: odim.height - refRect.height
};
/*
var transDelta = {
x: odim.x + odim.width / 2 - refRect.x - refRect.width / 2,
y: odim.y + odim.height / 2 - refRect.y - refRect.height / 2
//width: odim.width - refRect.width,
//height: odim.height - refRect.height
};
*/
this._translateDelta = transDelta;
this._initialScale = {
x: refRect.width / odim.width,
y: refRect.height / odim.height
};
if (this._computedTransMatrixInfo)
{
this._endScale = {
x: this._computedTransMatrixInfo.scaleX,
y: this._computedTransMatrixInfo.scaleY
};
this._endTranslate = {
x: this._computedTransMatrixInfo.tx,
y: this._computedTransMatrixInfo.ty
}
}
else
{
this._endScale = {x: 1, y: 1};
this._endTranslate = {x: 0, y: 0};
}
//console.log(this._translateDelta, refRect, odim);
},
/** @private */
setElementProp: function(element, position, options)
{
var compStyles = this.getComputedCssValues();
var transDelta = this._translateDelta;
var initialScale = this._initialScale;
var endScale = this._endScale;
var translateInfo = {
'x': this._endTranslate.x -(1 - position) * transDelta.x, // + 'px',
'y': this._endTranslate.y -(1 - position) * transDelta.y // + 'px'
};
var scaleInfo = {
'x': position * endScale.x + (1 - position) * initialScale.x,
'y': position * endScale.y + (1 - position) * initialScale.y
};
//var curr = {};
var style = element.style;
style.transformOrigin = '0 0 0';
//style.transformOrigin = '50% 50% 0';
/*
var sTransform = 'translate(' + translateInfo.x + ',' +translateInfo.y + ') ' + 'scaleX(' + scaleInfo.x + ') scaleY(' + scaleInfo.y + ')';
style.transform = sTransform;
*/
this.setElemTransform(element, translateInfo.x, translateInfo.y, scaleInfo.x, scaleInfo.y);
//console.log('set transform', position, sTransform);
}
});
};
/**
* A helper to create simple CSS3 transition executor class.
* @class
*/
Kekule.Widget.Css3TransitionSimpleClassCreator = {
/**
* Create a simple executor class.
* @param {String} className
* @param {String} cssProperty
* @param {String} timingFunc
* @param {Object} methods
* @returns {Class}
*/
create: function(className, cssProperty, timingFunc, methods)
{
/** @ignore */
var result = Class.create(Kekule.Widget.Css3Transition, {
CLASS_NAME: className,
initialize: function($super)
{
$super();
this.setCssProperty(cssProperty);
this.setTimingFunc(timingFunc);
}
});
if (methods)
result = ClassEx.extend(result, methods);
return result;
}
};
/** @ignore */
var TCC = Kekule.Widget.Css3TransitionSimpleClassCreator;
})();