/**
* @fileoverview
* Some helper classes for widgets.
* @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(){
/**
* Enumeration of modes to evoke/revoke associated widget.
* @class
*/
Kekule.Widget.EvokeMode = {
/** Alway be evoked or revoked. */
ALWAYS: 0,
/** Click on evokee widget. */
EVOKEE_CLICK: 1,
/** Mouse enter the evokee widget. */
EVOKEE_MOUSE_ENTER: 2,
/** Mouse leave the evokee widget. */
EVOKEE_MOUSE_LEAVE: 3,
/** Mouse down on the evokee widget. */
EVOKEE_MOUSE_DOWN: 4,
/** Mouse up on the evokee widget. */
EVOKEE_MOUSE_UP: 5,
/** Mouse move on evokee widget. */
EVOKEE_MOUSE_MOVE: 6,
/** Touch on the evokee widget. */
EVOKEE_TOUCH: 7,
/** Click on evoker widget. */
EVOKER_CLICK: 11,
/** Mouse enter the evoker widget. */
EVOKER_MOUSE_ENTER: 12,
/** Mouse leave the evoker widget. */
EVOKER_MOUSE_LEAVE: 13,
/** Mouse down on the evoker widget. */
EVOKER_MOUSE_DOWN: 14,
/** Mouse up on the evoker widget. */
EVOKER_MOUSE_UP: 15,
/** Mouse move on evoker widget. */
EVOKER_MOUSE_MOVE: 16,
/** Touch on the evoker widget. */
EVOKER_TOUCH: 17,
/** Timeout and not focused on evokee widget. */
EVOKEE_TIMEOUT: 21,
/** Timeout and not focused on evoker widget. */
EVOKER_TIMEOUT: 22
};
/** @ignore */
var EM = Kekule.Widget.EvokeMode;
/**
* A class help to evoke (show) / hide associated widgets.
* User should not use this class directly.
* @class
* @augments ObjectEx
*
* @property {Kekule.Widget.BaseWidget} evokee Widget to raise the evoking (usually the parent widget).
* @property {Kekule.Widget.BaseWidget} evoker Widget to be evoked (usually the child widget).
* @property {Array} evokeModes Modes to evoke (show) the evoker.
* @property {Array} revokeModes Modes to revoke (hide) the evoker.
* @property {Int} timeout If {@link Kekule.Widget.EvokeMode.EVOKEE_TIMEOUT} or {@link Kekule.Widget.EvokeMode.EVOKER_TIMEOUT} in mode,
* this property will decide after how many milliseconds of evoke, the revoke should be called.
* @property {Int} showHideType
* @private
*/
Kekule.Widget.DynamicEvokeHelper = Class.create(ObjectEx,
/** @lends Kekule.Widget.DynamicEvokeHelper# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.DynamicEvokeHelper',
/** @private */
evokeEvents: ['click', /*'mouseover', 'mouseout',*/ 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'touchstart'],
/** @private */
DEF_TIMEOUT: 5000,
/** @constructs */
initialize: function($super, evokee, evoker, evokeModes, revokeModes, timeout)
{
$super();
this._timeoutRevokeHandle = null; // private
this.reactEvokeEventsBind = this.reactEvokeEvents.bind(this); // IMPORTANT, linkWidgets use reactEvokeEventsBind
this.checkTimeoutRevokeBind = this.checkTimeoutRevoke.bind(this); // IMPORTAN, timeout settings may be used after linkWidgets
this.setTimeout(timeout || this.DEF_TIMEOUT);
//console.log('create', evokeModes, revokeModes);
this.setPropStoreFieldValue('evokeModes', evokeModes || []);
this.setPropStoreFieldValue('revokeModes', revokeModes || []);
this.linkWidgets(evokee, evoker);
},
/** @ignore */
finalize: function($super)
{
this.unlinkWidgets(this.getEvokee(), this.getEvoker());
$super();
},
/** @private */
initProperties: function()
{
this.defineProp('evokee', {'dataType': 'Kekule.Widget.BaseWidget', 'serializable': false, setter: null});
this.defineProp('evoker', {'dataType': 'Kekule.Widget.BaseWidget', 'serializable': false, setter: null});
this.defineProp('evokeModes', {'dataType': DataType.ARRAY});
this.defineProp('revokeModes', {'dataType': DataType.ARRAY});
this.defineProp('timeout', {'dataType': DataType.INT}); // in seconds
this.defineProp('showHideType', {'dataType': DataType.INT});
},
/**
* Invoke the evoker (usually shows it).
*/
evoke: function()
{
if (this.getEvoker())
{
// clear prev timeout handler (if exists)
clearTimeout(this._timeoutRevokeHandle);
this.getEvoker().show(this.getEvokee(), null, this.getShowHideType());
this.postponeTimeoutCheck();
}
},
/**
* Revoke the evoker (usually hide it).
*/
revoke: function()
{
if (this.getEvoker())
{
// clear prev timeout handler (if exists)
clearTimeout(this._timeoutRevokeHandle);
// set focus out from evoker
var doc = this.getEvoker().getDocument();
doc.body.focus();
// then hide the evoker
this.getEvoker().hide(this.getEvokee(), null, this.getShowHideType());
}
},
/** @private */
doObjectChange: function($super, modifiedPropNames)
{
$super(modifiedPropNames);
var inter = Kekule.ArrayUtils.intersect(modifiedPropNames, ['evokeModes', 'revokeModes']);
if (inter.length > 0)
this.evokeModesChanged();
},
/** @private */
linkWidgets: function(evokee, evoker)
{
this.setPropStoreFieldValue('evokee', evokee);
this.setPropStoreFieldValue('evoker', evoker);
this.installEventHandlers(evokee, this.evokeEvents, this.reactEvokeEventsBind);
this.installEventHandlers(evoker, this.evokeEvents, this.reactEvokeEventsBind);
this.updateOnAlwaysMode();
},
/** @private */
unlinkWidgets: function(evokee, evoker)
{
this.uninstallEventHandlers(evokee, this.evokeEvents, this.reactEvokeEventsBind);
this.uninstallEventHandlers(evoker, this.evokeEvents, this.reactEvokeEventsBind);
},
/** @private */
installEventHandlers: function(widget, events, handler)
{
for (var i = 0, l = events.length; i < l; ++i)
{
widget.addEventListener(events[i], handler);
}
},
/** @private */
uninstallEventHandlers: function(widget, events, handler)
{
for (var i = 0, l = events.length; i < l; ++i)
{
widget.removeEventListener(events[i], handler);
}
},
/** @private */
evokeModesChanged: function()
{
this.updateOnAlwaysMode();
},
/** @private */
updateOnAlwaysMode: function()
{
if (this.getEvokeModes().indexOf(EM.ALWAYS) >= 0) // always evoke
{
//console.log('updateOnAlwaysMode', this.getEvokeModes(), this.getRevokeModes(), this.getEvoker());
this.evoke();
//console.log('after evoke', this.getEvoker().getDisplayed());
}
else if (this.getRevokeModes().indexOf(EM.ALWAYS) >= 0) // always revoke
{
this.revoke();
}
},
/** @private */
checkTimeoutRevoke: function(onEvoke)
{
if (!this.getEvoker())
return;
var DU = Kekule.DomUtils;
/*
if (onEvoke) // evoke after a period of time
{
// do nothing here
}
else // revoke
*/
// TODO: currently do not support timeout evoke
{
var doc = this.getEvoker().getDocument();
var activeElem = doc.activeElement;
var modes = this.getRevokeModes();
var checkElems = [];
if (modes.indexOf(EM.EVOKEE_TIMEOUT) >= 0)
checkElems.push(this.getEvokee().getElement());
if (modes.indexOf(EM.EVOKER_TIMEOUT) >= 0)
checkElems.push(this.getEvoker().getElement());
var postpone = false;
for (var i = 0, l = checkElems.length; i < l; ++i)
{
var elem = checkElems[i];
if ((activeElem === elem) || DU.isDescendantOf(activeElem, elem))
{
postpone = true;
break;
}
}
if (postpone)
this.postponeTimeoutCheck();
else
this.revoke();
}
},
/** @private */
postponeTimeoutCheck: function()
{
if (this.hasTimeoutMode(this.getRevokeModes()))
{
//console.log(this.checkTimeoutRevokeBind, this.getTimeout());
if (this.getEvokeModes().indexOf(EM.ALWAYS) >= 0) // always evoke
return;
else
this._timeoutRevokeHandle = setTimeout(this.checkTimeoutRevokeBind, this.getTimeout());
}
},
/** @private */
hasTimeoutMode: function(evokeModes)
{
//return false;
return Kekule.ArrayUtils.intersect([EM.EVOKEE_TIMEOUT, EM.EVOKER_TIMEOUT], evokeModes).length > 0;
},
/** @private */
getEventTypeOfMode: function(evokeMode)
{
switch (evokeMode)
{
case EM.EVOKEE_CLICK:
case EM.EVOKER_CLICK: return 'click';
case EM.EVOKEE_MOUSE_ENTER:
case EM.EVOKER_MOUSE_ENTER: return 'mouseenter';
case EM.EVOKEE_MOUSE_LEAVE:
case EM.EVOKER_MOUSE_LEAVE: return 'mouseleave';
case EM.EVOKEE_MOUSE_DOWN:
case EM.EVOKER_MOUSE_DOWN: return 'mousedown';
case EM.EVOKEE_MOUSE_UP:
case EM.EVOKER_MOUSE_UP: return 'mouseup';
case EM.EVOKEE_MOUSE_MOVE:
case EM.EVOKER_MOUSE_MOVE: return 'mousemove';
case EM.EVOKEE_TOUCH:
case EM.EVOKER_TOUCH: return 'touchstart';
}
},
/** @private */
isEvokeTargetOnEvokee: function(evokeMode)
{
return (evokeMode >= EM.EVOKEE_CLICK) && (evokeMode <= EM.EVOKEE_TOUCH);
},
/** @private */
reactEvokeEvents: function(e)
{
var evtType = e.htmlEvent.getType();
var target = e.target;
if ((target !== this.getEvokee()) && (target !== this.getEvoker()))
return;
var isOnEvokee = (e.target === this.getEvokee());
var isEvoke = !this.getEvoker().isShown();
if (isEvoke)
{
if ((this.getRevokeModes().indexOf(EM.ALWAYS) >= 0)) // always revoke, do not show it
return;
var modes = this.getEvokeModes();
for (var i = 0, l = modes.length; i < l; ++i)
{
if (isOnEvokee === this.isEvokeTargetOnEvokee(modes[i])
&& (evtType === this.getEventTypeOfMode(modes[i]))) // meet
{
this.evoke();
return;
}
}
}
else
{
//console.log('ready to revoke', this.getEvokeModes().indexOf(EM.ALWAYS));
if (this.getEvokeModes().indexOf(EM.ALWAYS) >= 0) // always evoke, do not hide it
return;
var modes = this.getRevokeModes();
for (var i = 0, l = modes.length; i < l; ++i)
{
if (isOnEvokee === this.isEvokeTargetOnEvokee(modes[i])
&& (evtType === this.getEventTypeOfMode(modes[i]))) // meet
{
//console.log('helper', isOnEvokee, evtType, e);
this.revoke();
return;
}
}
}
}
});
})();