kekule
Version:
Open source JavaScript toolkit for chemoinformatics
388 lines (370 loc) • 12.1 kB
JavaScript
/*
* requires /lan/classes.js
* requires /core/kekule.common.js
* requires /utils/kekule.utils.js
* requires /utils/kekule.domUtils.js
* requires /xbrowsers/kekule.x.js
* requires /widget/kekule.widget.root.js
*/
(function(){
"use strict";
Kekule.globalOptions.add('widget.events', {
// Whether using mouse/touch events to simulate pointer events rather than using the native ones.
// There used to be bugs about native pointer events in some widget code causing pointleave
// fired at wrong position, so here we may set this option to true or false for debugging.
forceSimulatePointerEvent: false
});
/**
* A series of interactive events that may be handled by widget.
* @ignore
*/
Kekule.Widget.UiEvents = [
/*'blur', 'focus',*/ 'click', 'dblclick', 'mousedown',/*'mouseenter', 'mouseleave',*/ 'mousemove', 'mouseout', 'mouseover', 'mouseup', //'mousewheel',
'keydown', 'keyup', 'keypress',
'touchstart', 'touchend', 'touchcancel', 'touchmove',
'pointerdown', 'pointermove', 'pointerout', 'pointerover', 'pointerup',
'drag', 'dragend', 'dragenter', 'dragexit', 'dragleave', 'dragover', 'dragstart', 'drop'
];
/**
* A series of interactive events that must be listened on local element.
* @ignore
*/
Kekule.Widget.UiLocalEvents = [
'blur', 'focus', 'mouseenter', 'mouseleave', 'mousewheel', 'pointerenter', 'pointerleave'
];
/**
* A series of interactive touch gestures that may be handled by widget.
* @ignore
*/
Kekule.Widget.TouchGestures = [
//'press', 'pressup'
'hold', 'tap', 'doubletap',
'swipe', 'swipeup', 'swipedown', 'swipeleft', 'swiperight',
'transform', 'transformstart', 'transformend',
'rotate', 'rotatestart', 'rotatemove', 'rotateend', 'rotatecancel',
'pinch', 'pinchstart', 'pinchmove', 'pinchend', 'pinchcancel', 'pinchin', 'pinchout',
'pan', 'panstart', 'panmove', 'panend', 'pancancel', 'panleft', 'panright', 'panup', 'pandown'
];
var AU = Kekule.ArrayUtils;
/**
* A special class to check if an HTML event matches for certain condition.
* @class
* @augments ObjectEx
*
* @param {Hash} eventParams Including {type/eventType, and additional event params}.
*
* @property {String} eventType HTML event name.
* @property {Hash} eventParams Event params, including event type(name) and additional params, e.g. key and modifier state in key event.
*/
Kekule.Widget.HtmlEventMatcher = Class.create(ObjectEx,
/** @lends Kekule.Widget.HtmlEventMatcher# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.HtmlEventMatcher',
/** @constructs */
initialize: function(eventParams)
{
this.tryApplySuper('initialize', []);
this.setEventParams(eventParams || {});
},
/** @private */
initProperties: function()
{
this.defineProp('eventParams', {'dataType': DataType.HASH});
this._defineEventParamProp('eventType', DataType.STRING, 'type');
},
/** @ignore */
doFinalize: function()
{
this.tryApplySuper('doFinalize');
},
/** @private */
_defineEventParamProp: function(propName, dataType, paramFieldName)
{
var kname = propName || paramFieldName;
return this.defineProp(propName, {'dataType': dataType, 'serializable': false,
'getter': function() { return this.getEventParams()[kname]; },
'setter': function(value) { this.getEventParams()[kname] = value; }
});
},
/**
* Check if current handler matches an HTMLEvent, and should react to it.
* Descendants should override this method.
* @param {HTMLEvent} htmlEvent
* @private
*/
match: function(htmlEvent)
{
var evType = (htmlEvent.getType && htmlEvent.getType()) || htmlEvent.type;
return (evType === this.getEventType());
},
/**
* Execute action when matching an HTML event.
* @param {HTMLEvent} htmlEvent
* @param {Variant} execTarget Can be a function, Widget.Action or Widget.BaseWidget.
* @returns {Bool} Whether the event is matched and target executed.
*/
execOnMatch: function(htmlEvent, execTarget)
{
var result = this.match(htmlEvent);
if (result && execTarget)
{
result = this.doExecTarget(htmlEvent, execTarget);
}
return result;
},
/** @private */
doExecTarget: function(htmlEvent, target)
{
var result;
if (DataType.isFunctionValue(target))
result = target.apply(null, [htmlEvent, this]);
else if (target.execute && DataType.isFunctionValue(target.execute))
{
if (DataType.isObjectExValue(target))
{
if (target instanceof Kekule.Widget.BaseWidget)
result = target.execute(htmlEvent);
else if (target instanceof Kekule.Action)
result = target.execute(this, htmlEvent);
else
result = target.execute(htmlEvent, this);
}
else
result = target.execute(htmlEvent, this);
}
else
result = false;
// if the return value of target.execute is undefined, we assume it did do something
if (Kekule.ObjUtils.isUnset(result))
result = true;
return result;
}
});
/**
* A special class to react to HTML event and execute certain actions.
* @class
* @augments ObjectEx
*
* @param {Kekule.Widget.HtmlEventMatcher} matcher Matcher instance to test on events.
* @param {Variant} execTarget Function or action or widget that should be executed on matching event.
* @param {Bool} exclusive If true, event matching this one will not be dispatched further to other event responsers.
*
* @property {String} eventType HTML event name.
* @property {Hash} eventParams Additional event params, e.g. key and modifier state in key event.
* @property {Variant} execTarget Function or action or widget that should be executed on matching event.
* @property {Bool} exclusive If true, event matching this one will not be dispatched further to other event responsers.
*/
/**
* Invoked when a HTML event is matched (and execTarget should be executed).
* event param of it has fields: {htmlEvent, execTarget}.
* Note this event will still be invoked even if execTarget is not set.
* @name Kekule.Widget.HtmlEventResponser#execute
* @event
*/
Kekule.Widget.HtmlEventResponser = Class.create(ObjectEx,
/** @lends Kekule.Widget.HtmlEventResponser# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.HtmlEventResponser',
/** @constructs */
initialize: function(eventMatcher, execTarget, exclusive)
{
this.setPropStoreFieldValue('eventMatcher', eventMatcher);
this.setPropStoreFieldValue('execTarget', execTarget);
this.tryApplySuper('initialize', []);
this.setExclusive(exclusive || false);
},
/** @private */
initProperties: function()
{
this._defineMatcherRelatedProp('eventParams', {'dataType': DataType.HASH});
this._defineMatcherRelatedProp('eventType', DataType.STRING, 'type');
this.defineProp('execTarget', {'dataType': DataType.VARIANT, 'serializable': false});
this.defineProp('exclusive', {'dataType': DataType.BOOL, 'serializable': false});
this.defineProp('eventMatcher', {
'dataType': 'Kekule.Widget.HtmlEventMatcher', 'serializable': false, 'setter': null,
'scope': Class.PropertyScope.PRIVATE
});
},
/** @ignore */
doFinalize: function()
{
this.setExecTarget(null);
var matcher = this.getEventMatcher();
if (matcher)
matcher.finalize();
this.tryApplySuper('doFinalize');
},
/** @private */
_defineMatcherRelatedProp: function(propName, dataType, paramFieldName)
{
var kname = propName || paramFieldName;
return this.defineProp(propName, {'dataType': dataType,
'getter': function() {
return this.getEventMatcher().getPropValue(kname);
},
'setter': function(value) {
this.getEventMatcher().setPropValue(kname, value);
}
});
},
/**
* If matches, reacts to an HTML event.
* @param {HTMLEvent} htmlEvent
* @returns {Bool} A flag, false means the event can be propagate to other handlers; value other than false will stop the propagatation.
*/
reactTo: function(htmlEvent)
{
var result = false;
var matcher = this.getEventMatcher();
if (matcher)
{
result = matcher.match(htmlEvent);
if (result)
{
var execTarget = this.getExecTarget();
if (execTarget)
result = matcher.doExecTarget(htmlEvent, execTarget);
result = result && this.getExclusive();
this.invokeEvent('execute', {'htmlEvent': htmlEvent, 'execTarget': execTarget});
}
}
return result;
}
});
/**
* A special util class to dispatch HTML event to registered HTML event responsers.
* @class
* @augments ObjectEx
*/
Kekule.Widget.HtmlEventDispatcher = Class.create(ObjectEx,
/** @lends Kekule.Widget.HtmlEventDispatcher# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.HtmlEventDispatcher',
/** @constructs */
initialize: function()
{
this.setPropStoreFieldValue('responsers', {});
this.tryApplySuper('initialize', []);
},
/** @private */
initProperties: function()
{
// a hash map of all registered event handlers
this.defineProp('responsers', {
'dataType': DataType.HASH,
'scope': Class.PropertyScope.PRIVATE,
'serializable': false,
'setter': null
});
},
/** @ignore */
doFinalize: function()
{
// free all registered handlers
var responsers = this.getResponsers();
var evTypes = Object.getOwnPropertyNames(responsers);
for (var i = 0, ii = evTypes.length; i < ii; ++i)
{
var subItems = responsers[evTypes[i]];
if (subItems && subItems.length)
{
for (var j = subItems.length - 1; j >= 0; --j)
{
subItems[j].finalize();
}
}
}
this.tryApplySuper('doFinalize');
},
/**
* Returns all registered handlers for an event type.
* @param {String} eventType
* @param {Bool} canCreateArray
* @private
*/
getResponsersForType: function(eventType, canCreateArray)
{
var subItems = this.getResponsers()[eventType];
if (!subItems)
{
subItems = [];
this.getResponsers()[eventType] = subItems;
}
return subItems;
},
/**
* Register an event responser.
* @param {Kekulw.Widget.HtmlEventResponser} handler
*/
registerResponser: function(responsers)
{
var evType = responsers.getEventType();
if (evType) // bypass the ones without explicit event type
{
var subItems = this.getResponsersForType(evType, true);
AU.pushUnique(subItems, responsers);
}
},
/**
* Unregister an event responser.
* @param {Kekulw.Widget.HtmlEventResponser} responsers
* @param {Bool} doFinalize If true, the unregistered handler will also be finalized.
*/
unregisterResponser: function(responsers, doFinalize)
{
var evType = responsers.getEventType();
if (evType) // bypass the ones without explicit event type
{
var subItems = this.getResponsersForType(evType);
if (subItems && subItems.length)
{
var index = subItems.indexOf(responsers);
if (index >= 0)
{
subItems.splice(index, 1);
if (doFinalize)
responsers.finalize();
}
}
}
},
/**
* Create and register a new event responser.
* @param {Kekule.Widget.HtmlEventMatcher} eventMatcher
* @param {Variant} execTarget
* @param {Bool} exclusive
* @returns {Kekulw.Widget.HtmlEventResponser}
*/
addResponser: function(eventMatcher, execTarget, exclusive)
{
var r = new Kekule.Widget.HtmlEventResponser(eventMatcher, execTarget, exclusive);
this.registerResponser(r);
return r;
},
/**
* Dispatch an HTML event, find suitable handlers reacting to it.
* @param {HTMLEvent} event
*/
dispatch: function(event)
{
var evType = (event.getType && event.getType()) || event.type;
if (evType)
{
var subItems = this.getResponsersForType(evType);
if (subItems && subItems.length)
{
for (var i = subItems.length - 1; i >= 0; --i) // dispatch from the newer to older
{
var responser = subItems[i];
var exclusive = responser.reactTo(event);
if (!!exclusive || event.cancelBubble) // a exclusive handler, stop propagation
break;
}
}
}
}
});
})();