kekule
Version:
Open source JavaScript toolkit for chemoinformatics
1,563 lines (1,498 loc) • 43 kB
JavaScript
/**
* @fileoverview
* Base file of kekule XBrowser lib.
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* require /core/kekule.root.js
* require /utils/kekule.utils.js
* require /utils/kekule.domUtils.js
*/
(function ($root)
{
;
if (typeof(window) !== 'undefined') // has window object
$root = window;
var win = $root, document = win && win.document;
if (typeof(Kekule) === 'undefined')
Kekule = {};
if (typeof(navigator) === "undefined") // not in browser environment, node.js?
{
Kekule.Browser = {};
Kekule.BrowserFeature = {};
}
else
{ // start of browser detect part
/**
* Browser Check.
* @class
*/
Kekule.Browser = {
IE: !!(win.attachEvent && !win.opera),
Opera: !!win.opera,
WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('like Gecko') < 0 && navigator.userAgent.indexOf('KHTML') == -1,
MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/),
language: navigator.language || navigator.browserLanguage // language of broweser
};
Kekule.Browser.IEVersion = Kekule.Browser.IE && (function(){
var agent = navigator.userAgent.toLowerCase();
return (agent.indexOf('msie') !== -1) ? parseInt(agent.split('msie')[1]) : false
})();
/**
* Browser HTML5 feature check.
* Code copy from https://github.com/mrdoob/three.js/blob/master/examples/js/Detector.js
* @class
*/
Kekule.BrowserFeature = {
typedArray: (typeof(ArrayBuffer) !== 'undefined'),
svg: !!win.SVGSVGElement,
canvas: !! win.CanvasRenderingContext2D,
webgl: (function()
{
//if (Kekule.BrowserFeature.webgl === undefined)
{
var result =
(function()
{
try
{
var canvas = document.createElement('canvas');
return !!win.WebGLRenderingContext && ( canvas.getContext('webgl') || canvas.getContext('experimental-webgl') );
}
catch (e)
{
return false;
}
})();
//Kekule.BrowserFeature.webgl = result;
}
//return Kekule.BrowserFeature.webgl;
return !!result;
})(),
htmlTemplate: !!win.HTMLTemplateElement,
htmlSlot: !!win.HTMLSlotElement,
downloadHref: (function(doc){ return 'download' in doc.createElement('a')})(document),
blob: !!win.Blob,
workers: !! win.Worker,
fileapi: !!(win.File && win.FileReader && win.FileList && win.Blob),
sessionStorage: (function() { try { return !!win.sessionStorage} catch(e) { return false} })(), // directly call session storage locally on Firefox now will cause exception
localStorage: (function() { try { return !!win.localStorage} catch(e) { return false} })(), // !!win.localStorage,
cssTransition: (function(s) {
return 'transition' in s || 'WebkitTransition' in s || 'MozTransition' in s || 'msTransition' in s || 'OTransition' in s;
})(document.createElement('div').style),
cssTranform: (function(s) {
return 'transform' in s || 'WebkitTransform' in s || 'MozTransform' in s || 'msTransform' in s || 'OTransform' in s;
})(document.createElement('div').style),
cssFlex: (function(s) {
return 'flex' in s || 'WebkitFlex' in s || 'MozFlex' in s || 'msFlex' in s || 'OFlex' in s;
})(document.createElement('div').style),
cssGrid: (function(s) {
return 'grid' in s || 'WebkitGrid' in s || 'MozGrid' in s || 'msGrid' in s || 'OGrid' in s;
})(document.createElement('div').style),
html5Form: {
placeholder: (function(elem){ return 'placeholder' in elem; })(document.createElement('input')),
supportType: function(typeName)
{
var elem = document.createElement('input');
elem.setAttribute('type', typeName);
var result = elem.type === typeName;
var textTypes = ['text', 'url', 'search'];
if (result && (textTypes.indexOf(typeName.toLowerCase()) < 0))
{
var testValue = ':)';
elem.value = testValue;
result = elem.value !== testValue;
}
return result;
}
},
mutationObserver: win.MutationObserver || win.MozMutationObserver || win.WebkitMutationObserver,
resizeObserver: !!win.ResizeObserver,
touchEvent: !!win.touchEvent,
pointerEvent: !!win.PointerEvent,
draggable: (function() {
var div = document.createElement('div');
return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
})(),
webAssembly: (typeof(win.WebAssembly) === 'object' && typeof(win.WebAssembly.instantiate) === 'function')
};
} // end of browser detect part
// polyfill of requestAnimationFrame / cancelAnimationFrame
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !win.requestAnimationFrame; ++x) {
win.requestAnimationFrame = win[vendors[x]+'RequestAnimationFrame'];
win.cancelAnimationFrame = win[vendors[x]+'CancelAnimationFrame']
|| win[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!win.requestAnimationFrame)
win.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = win.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!win.cancelAnimationFrame)
win.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
/**
* Namespace for XBrowser lib.
* @namespace
*/
Kekule.X = {};
/** @ignore */
var X = Kekule.X;
var isUnset = function(o)
{
return ((o === null) || (typeof(o) === 'undefined'));
};
var notUnset = function(o)
{
return !isUnset(o);
};
var isElemPositioned = function(element)
{
var pos = win.getComputedStyle? win.getComputedStyle(element, null).position:
element.currentStyle? element.currentStyle.position: null;
if (!pos)
return false;
else
{
if (!pos)
return false;
else
{
//console.log('position', pos.toString());
pos = pos.toString().toLowerCase();
return (pos === 'relative') || (pos === 'absolute');
}
}
};
/////////////////////////////////////////////////////////////
// DOM mutation observer
/////////////////////////////////////////////////////////////
X.MutationObserver = win.MutationObserver || win.MozMutationObserver || win.WebkitMutationObserver;
/////////////////////////////////////////////////////////////
// Cross browser event handling supporting
/////////////////////////////////////////////////////////////
/**
* Implementation of cross browser event handling.
* @class
*/
X.Event = {
/** @lends Kekule.X.Event */
// Constants about event argument
/** @deprecated */
MOUSE_BTN_LEFT: 0,
/** @deprecated */
MOUSE_BTN_RIGHT: 2,
/** @deprecated */
MOUSE_BTN_MID: 1,
/** @deprecated */
MOUSE_BTN_LR: 3
};
/**
* A serials of constants of pointer types.
*/
X.Event.PointerType = {
MOUSE: 'mouse',
TOUCH: 'touch',
PEN: 'pen'
};
/**
* A serials of constants of mouse button flags.
*/
X.Event.MouseButton = {
LEFT: 0,
RIGHT: 2,
MID: 1,
LR: 3
};
/**
* A serials of constants of key codes
*/
X.Event.KeyCode = {
BACKSPACE: 8,
TAB: 9,
CLEAR: 12,
ENTER: 13,
SHIFT: 16,
CTRL: 17,
ALT: 18,
PAUSE: 19,
CAPSLOCK: 20,
ESC: 27,
SPACE: 32,
PAGEUP: 33,
PAGEDOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
PRTSC: 44, // print screen
INS: 45,
DEL: 46,
F1: 112,
F2: 113,
F3: 114,
F4: 115,
F5: 116,
F6: 117,
F7: 118,
F8: 119,
F9: 120,
F10: 121,
F11: 122,
F12: 123,
NUMLOCK: 144,
SCROLLLOCK: 145
};
/**
* Check if a event type is supported by browser.
* Code is borrowed from http://www.htmlgoodies.com/html5/javascript/detecting-browser-event-support.html#fbid=WOHkKIQwoce
* @param {String} eventName
* @returns {Bool}
*/
X.Event.isSupported = (function()
{
var cache = {};
return function(eventName) {
if (eventName.indexOf('touch') === 0) // touch events
return Kekule.BrowserFeature.touchEvent;
if (eventName.indexOf('pointer') === 0) // pointer events
return Kekule.BrowserFeature.pointerEvent;
var TAGNAMES = {
'select': 'input',
'change': 'input',
'submit': 'form',
'reset' : 'form',
'error' : 'img',
'load' : 'img',
'abort' : 'img',
'unload': 'win',
'resize': 'win'
},
shortEventName = eventName.replace(/^on/, '');
if(cache[shortEventName]) { return cache[shortEventName]; }
var elt = TAGNAMES[shortEventName] == 'win'
? win
: document.createElement(TAGNAMES[shortEventName] || 'div');
eventName = 'on'+shortEventName;
var eventIsSupported = (eventName in elt);
if (!eventIsSupported) {
elt.setAttribute(eventName, 'return;');
eventIsSupported = typeof elt[eventName] == 'function';
}
elt = null;
cache[shortEventName] = eventIsSupported;
return eventIsSupported;
};
})();
X.Event.Methods = {
/** @lends Kekule.X.Event */
// methods to get general event information
/**
* Get event.type string.
* @param {Object} event
* @returns {String}
*/
getType: function(event)
{
return event.__$type__ || event.type;
},
/** @ignore */
setType: function(event, value)
{
event.__$type__ = value;
try
{
event.type = value;
}
catch(e)
{
}
},
getPointerType: function(event)
{
return event.pointerType;
},
/**
* Get event.target element.
* @param {Object} event
* @returns {Object}
*/
getTarget: function(event)
{
var target = event.__$target__ || event.target || event.srcElement;
if (target.nodeType == 3) // defeat Safari bug
target = target.parentNode;
return target;
},
/**
* Some times we may need to overwrite the target of event (e.g., in mapping touch event to pointer).
* Writing directly to event.target often does not change the actual value, so we use a special field to store it.
* @param {Object} event
*/
setTarget: function(event, newTarget)
{
event.__$target__ = newTarget;
try
{
event.target = newTarget;
}
catch(e)
{
}
},
/*
* Get event.currTarget element.
* @param {Object} event
* @returns {Object}
*/
/*
getCurrTarget: function(event)
{
return event.currentTarget;
},
*/
/**
* Get event.currTarget element.
* @param {Object} event
* @returns {Object}
*/
getCurrentTarget: function(event)
{
return event.currentTarget;
},
/**
* Get event.eventPhase.
* @param {Object} event
* @returns {Int}
*/
getEventPhase: function(event)
{
return event.eventPhase;
},
// methods to get mouse/key event information
/**
* Get event.altKey.
* @param {Object} event
* @returns {Bool}
*/
getAltKey: function(event)
{
return event.altKey;
},
/**
* Get event.ctrlKey.
* @param {Object} event
* @returns {Bool}
*/
getCtrlKey: function(event)
{
return event.ctrlKey;
},
/**
* Get event.shiftKey.
* @param {Object} event
* @returns {Bool}
*/
getShiftKey: function(event)
{
return event.shiftKey;
},
/**
* Get event.mataKey.
* @param {Object} event
* @returns {Bool}
*/
getMetaKey: function(event)
{
return event.metaKey;
},
/**
* Get event.repeat (key down repeat flag).
* @param {Object} event
* @returns {Bool}
*/
getRepeat: function(event)
{
return event.repeat;
},
/**
* Get event.charCode.
* @param {Object} event
* @returns {Int}
*/
getCharCode: function(event)
{
// TODO: too rough, need further development
// ref: http://www.quirksmode.org/js/keys.html
return notUnset(event.charCode)? event.charCode: event.keyCode;
},
/**
* Get event.keyCode.
* @param {Object} event
* @returns {Int}
*/
getKeyCode: function(event)
{
// TODO: too rough, need further development
// ref: http://www.quirksmode.org/js/keys.html
return event.keyCode;
},
/**
* Get event.key.
* @param {Object} event
* @returns {Bool}
*/
getKey: function(event)
{
return event.key;
},
/**
* Get event.code.
* @param {Object} event
* @returns {Bool}
*/
getCode: function(event)
{
return event.code;
},
/**
* Get event.clientX.
* @param {Object} event
* @returns {Int}
*/
getClientX: function(event)
{
var result;
if (event.touches) // touch event
{
var touch = event.touches[0];
result = touch && touch.clientX;
}
if (result === undefined)
result = event.clientX;
return result;
},
/**
* Get event.clientY.
* @param {Object} event
* @returns {Int}
*/
getClientY: function(event)
{
var result;
if (event.touches) // touch event
{
var touch = event.touches[0];
result = touch && touch.clientY;
}
if (result === undefined)
result = event.clientY;
return result;
},
/**
* Get event.screenX.
* @param {Object} event
* @returns {Int}
*/
getScreenX: function(event)
{
var result;
if (event.touches) // touch event
{
var touch = event.touches[0];
result = touch && touch.screenX;
}
if (result === undefined)
result = event.screenX;
return result;
},
/**
* Get event.screenY.
* @param {Object} event
* @returns {Int}
*/
getScreenY: function(event)
{
var result;
if (event.touches) // touch event
{
var touch = event.touches[0];
result = touch && touch.screenY;
}
if (result === undefined)
result = event.screenY;
return result;
},
/**
* Get X coordinate related to document page.
* @param {Object} event
* @returns {Int}
*/
getPageX: function(event)
{
var result;
if (event.touches)
{
var touch = event.touches[0];
if (touch && notUnset(touch.pageX))
result = touch.pageX;
}
if (result === undefined && notUnset(event.pageX)) // touchmove event may still has pageX/Y property, so check this afterward
result = event.pageX;
//else // fallback
if (result === undefined)
{
var doc = X.Event.getTarget(event).ownerDocument || X.Event.getTarget(event);
var body = doc? doc.body: null;
result = X.Event.getClientX(event) + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
}
return result;
},
/**
* Get Y coordinate related to document page.
* @param {Object} event
* @returns {Int}
*/
getPageY: function(event)
{
var result;
if (event.touches)
{
var touch = event.touches[0];
if (touch && notUnset(touch.pageY))
return touch.pageY;
}
else if (result === undefined && notUnset(event.pageY))
return event.pageY;
if (result === undefined) // fallback
{
var doc = X.Event.getTarget(event).ownerDocument || X.Event.getTarget(event);
var body = doc? doc.body: null;
result = X.Event.getClientY(event) + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
}
return result;
},
/**
* Returns the mouse X coordinates relative to the event's target.
* @param {Object} event
* @returns {Int}
*/
getOffsetX: function(event)
{
if (notUnset(event.offsetX))
return event.offsetX;
else // Gecko
{
var elem = X.Event.getTarget(event);
if ((elem.defaultView || elem.parentWindow) && elem.body) // is document
elem = elem.body;
if (notUnset(event.layerX) && isElemPositioned(elem) && !event.touches) // check if target is a relative or absolute element, if so layerX ~= offsetX
{
return event.layerX;
}
else
{
var clientX = X.Event.getClientX(event);
//return Math.round(clientX - elem.getBoundingClientRect().left);
return Math.round(clientX - Kekule.HtmlElementUtils.getElemPagePos(elem, true).x);
}
}
},
/**
* Returns the mouse Y coordinates relative to the event's target.
* @param {Object} event
* @returns {Int}
*/
getOffsetY: function(event)
{
if (notUnset(event.offsetY))
return event.offsetY;
else // Gecko
{
var elem = X.Event.getTarget(event);
if ((elem.defaultView || elem.parentWindow) && elem.body) // is document
elem = elem.body;
if (notUnset(event.layerY) && isElemPositioned(elem) && !event.touches) // check if target is a relative or absolute element, if so layerX ~= offsetX
{
return event.layerY;
}
else
{
var clientY = X.Event.getClientY(event);
//return Math.round(clientY - elem.getBoundingClientRect().top);
return Math.round(clientY - Kekule.HtmlElementUtils.getElemPagePos(elem, true).y);
}
}
},
/**
* Returns x/y coord of event.
* @param {Object} event
* @param {Bool} considerCssTransform
*/
getOffsetCoord: function(event, considerCssTransform)
{
var elem = X.Event.getTarget(event);
var transformMatrix;
if (considerCssTransform && Kekule.ObjUtils.isUnset(event.offsetX)) // has no native offsetX, may need calculation
{
transformMatrix = Kekule.StyleUtils.getTotalTransformMatrix(elem);
}
if (transformMatrix) // elem has transform, calculate
{
// calculation
var clientX = X.Event.getClientX(event);
var clientY = X.Event.getClientY(event);
var coord = {
x: clientX - Kekule.HtmlElementUtils.getElemBoundingClientRect(elem).x,
y: clientY - Kekule.HtmlElementUtils.getElemBoundingClientRect(elem).y
};
var invertMatrix = Kekule.StyleUtils.calcInvertTransformMatrix(transformMatrix);
if (invertMatrix)
{
var vector = Kekule.MatrixUtils.create(3, 1);
vector[0][0] = coord.x;
vector[1][0] = coord.y;
vector[2][0] = 1;
var result = Kekule.MatrixUtils.multiply(invertMatrix, vector);
coord.x = result[0][0];
coord.y = result[1][0];
return coord;
}
}
// when calculation fails or has no transform
return {'x': X.Event.getOffsetX(event), 'y': X.Event.getOffsetY(event)};
},
/**
* Returns the mouse X coordinates relative to the top-left of window.
* @param {Object} event
* @returns {Int}
*/
getWindowX: function(event)
{
var x = X.Event.getPageX(event);
var doc = event.target.ownerDocument || event.target;
var win = doc && (doc.defaultView || doc.parentWindow);
var delta = (win && win.scrollX) || 0;
return x - delta;
},
/**
* Returns the mouse Y coordinates relative to the top-left of window.
* @param {Object} event
* @returns {Int}
*/
getWindowY: function(event)
{
var y = X.Event.getPageY(event);
var doc = event.target.ownerDocument || event.target;
var win = doc && (doc.defaultView || doc.parentWindow);
var delta = (win && win.scrollY) || 0;
return y - delta;
},
/**
* Returns the mouse X coordinates relative to the event's currTarget.
* @param {Object} event
* @returns {Int}
*/
getRelXToCurrTarget: function(event)
{
var elem = X.Event.getCurrentTarget(event);
if ((elem.defaultView || elem.parentWindow) && elem.body) // is document
elem = elem.body;
var clientX = X.Event.getClientX(event);
//return Math.round(clientX - elem.getBoundingClientRect().left);
return Math.round(clientX - Kekule.HtmlElementUtils.getElemPagePos(elem, true).left);
},
/**
* Returns the mouse Y coordinates relative to the event's currTarget.
* @param {Object} event
* @returns {Int}
*/
getRelYToCurrTarget: function(event)
{
var elem = X.Event.getCurrentTarget(event);
if ((elem.defaultView || elem.parentWindow) && elem.body) // is document
elem = elem.body;
var clientY = X.Event.getClientY(event);
//console.log('y', clientY, elem.getBoundingClientRect().top, elem.tagName);
//return Math.round(clientY - elem.getBoundingClientRect().top);
return Math.round(clientY - Kekule.HtmlElementUtils.getElemPagePos(elem, true).top);
},
/**
* Returns touches array of event in touch.
* @param {Object} event
* @returns {Array}
*/
getTouches: function(event)
{
return event.touches;
}
};
/** @ignore */
X.Event._MouseEventEx = {
isMouseEnterLeaveEvent: function(eventType)
{
return (eventType === 'mouseenter') || (eventType === 'mouseleave');
},
addMouseEnterLeaveListener: function(element, eventType, handler, useCapture)
{
var isSupported = ((eventType === 'mouseenter') && X.Event.isSupported('mouseenter'))
|| ((eventType === 'mouseleave') && X.Event.isSupported('mouseleave'));
//isSupported = false; // debug
if (isSupported)
//return X.Event.addListener(element, eventType, handler, useCapture); // recursion
return element.addEventListener(eventType, handler, useCapture); // IE support leave/enter event and has no extra code added, so we can use w3c method directly
else
{
var wrapper = function(e)
{
var target = e.getTarget? e.getTarget(): e.target;
var currTarget = e.getCurrentTarget? e.getCurrentTarget(): e.currentTarget; // is actually element
if ((target === currTarget) || (Kekule.DomUtils.isDescendantOf(target, currTarget))) // raised by self or child, all ok
{
var related = e.getRelatedTarget? e.getRelatedTarget(): e.relatedTarget;
if ((!related) // related not set, may move out of window
|| (!Kekule.DomUtils.isDescendantOf(related, currTarget))) // and not cross to child element
{
/*
if (eventType === 'mouseleave')
console.log('wrapper', eventType, target, related, currTarget);
*/
//debug
/*
if (currTarget && currTarget.style)
currTarget.style.backgroundColor = 'red';
console.log('LEAVE');
*/
// use a new event object, avoid overwrite the original mouseover/mouseout infos
var event = new MouseEvent(eventType, {
'view': win,
'bubbles': false,
'cancelable': true,
'target': currTarget,
'currentTarget': currTarget,
'relatedTarget': related
});
//e.__$type__ = eventType;
handler.call(currTarget, event);
//e.stopPropagation();
/*
// fire a custom mouseleave event
currTarget.dispatchEvent(event);
console.log('fire: ', eventType);
*/
}
/*
// debug
else
{
if (currTarget && currTarget.style)
currTarget.style.backgroundColor = 'yellow';
}
*/
}
};
handler.__$mouseExListenerWrapper__ = wrapper;
var newType = (eventType === 'mouseenter')? 'mouseover': 'mouseout';
//return X.Event.addListener(element, newType, wrapper, useCapture);
return element.addEventListener(newType, wrapper, useCapture);
}
},
removeMouseEnterLeaveListener: function(element, eventType, handler)
{
var isSupported = ((eventType === 'mouseenter') && X.Event.isSupported('mouseenter'))
|| ((eventType === 'mouseleave') && X.Event.isSupported('mouseleave'));
if (isSupported)
//return Kekule.X.Event.removeListener(element, eventType, handler);
return element.removeEventListener(eventType, handler); // IE support leave/enter event and has no extra code added, so we can use w3c method directly
else
{
var newType = (eventType === 'mouseenter')? 'mouseover': 'mouseout';
//return X.Event.removeListener(element, newType, handler.__$listenerWrapper__ || handler);
return element.removeEventListener(newType, handler.__$mouseExListenerWrapper__ || handler);
}
}
};
X.Event._W3C =
/** @lends Kekule.X.Event */
{
/**
* Add an event listener to element.
* @param {Object} element HTML element.
* @param {String} eventType W3C name of event, such as 'click'.
* @param {Function} handler Event handler.
* @param {Hash} options Listener options. IE (attachEvent) will ignore this paramter.
*/
addListener: function(element, eventType, handler, options)
{
if (X.Event._MouseEventEx.isMouseEnterLeaveEvent(eventType))
return X.Event._MouseEventEx.addMouseEnterLeaveListener(element, eventType, handler, options);
else
return element.addEventListener(eventType, handler, options);
},
/**
* Remove an event listener from element.
* @param {Object} element HTML element.
* @param {String} eventType W3C name of event, such as 'click'.
* @param {Function} handler Event handler.
* @param {Bool} options Listener options. IE (attachEvent) will ignore this paramter.
*/
removeListener: function(element, eventType, handler, options)
{
if (X.Event._MouseEventEx.isMouseEnterLeaveEvent(eventType) && handler.__$listenerWrapper__)
return X.Event._MouseEventEx.removeMouseEnterLeaveListener(element, eventType, handler.__$listenerWrapper__);
else
return element.removeEventListener(eventType, handler, options);
},
/**
* Stop the propagation of event.
* @param {Object} event
*/
stopPropagation: function(event)
{
event.stopPropagation();
},
/**
* Prevents the default action for the event.
* @param {Object} event
*/
preventDefault: function(event)
{
event.preventDefault();
}
};
/** @ignore */
X.Event._W3CMethods = {
/** @lends Kekule.X.Event */
/**
* Get event.relatedTarget element.
* @param {Object} event
* @returns {Object}
*/
getRelatedTarget: function(event)
{
return event.relatedTarget;
},
// methods to get mouse event information
/**
* Get event.button.
* @param {Object} event
* @returns {Int}
*/
getButton: function(event)
{
return event.button;
}
};
/** @ignore */
X.Event._Gecko = {
addListener: function(element, eventType, handler, useCapture)
{
if (X.Event._MouseEventEx.isMouseEnterLeaveEvent(eventType))
{
return X.Event._MouseEventEx.addMouseEnterLeaveListener(element, eventType, handler, useCapture);
}
if ((eventType === 'mousewheel'))
{
var wrapper = function(e)
{
//var e = Object.extend({}, evt); // must create a new object, otherwise e.type can not be changed
//e.prototype = evt.prototype;
// change DOMMouseScroll detail to delta
e.wheelDeltaY = e.wheelDelta = -e.detail * 40; // detail usually be 3 while delta be 120, direction is opposed
e.__$type__ = 'mousewheel'; // e.type can not be changed, so use another field to save the new type
handler.call(element, e);
};
handler.__$mousewheelListenerWrapper__ = wrapper;
element.addEventListener('DOMMouseScroll', wrapper, useCapture);
}
else
element.addEventListener(eventType, handler, useCapture);
},
removeListener: function(element, eventType, handler, useCapture)
{
if ((eventType === 'mousewheel') && handler.__$mousewheelListenerWrapper__)
element.removeEventListener('DOMMouseScroll', handler.__$mousewheelListenerWrapper__, useCapture);
else
element.removeEventListener(eventType, handler, useCapture);
}
};
/** @ignore */
X.Event._IE = {
/** @private */
_handlers: {}, // store all registered handlers
/** @private */
WIN_UNLOAD_FLAG_FIELD: '__$win_unload_linked__',
addListener: function(element, eventType, handler, useCapture)
{
// Since IE support mouseenter/mouseleave from the beginning, need not to add extra X.Event._MouseEventEx code here
//console.log('add event listener', eventType);
if (X.Event.findHandlerIndex(element, eventType, handler) >= 0) // already registered, exit
return;
var wrapper = (function(e)
{
if (!e)
e = win.event;
// add essential W3C fields
e.currentTarget = element;
e.eventPhase = (e.srcElement === element)? 2: 3;
/*
// wrap e as a W3C standard event arg
var event = {
_ieEvent: e,
type: e.type,
target: e.srcElement,
currentTarget: element,
relatedTarget: e.fromElement || e.toElement,
eventPhase: (e.srcElement === element)? 2: 3,
clientX: e.clientX,
clientY: e.clientY,
screenX: e.screenX,
screenY: e.screenY,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
charCode: e.keyCode,
// event management methods
stopPropagation: function() { this._ieEvent.cancelBubble = true; },
preventDefault: function() {this._ieEvent.returnValue = false; }
};
*/
if (!hasEventPrototype) // IE7, can not extend e by prototype
Object.extend(e, eventObjMethods);
handler.call(element, e);
});
// attach to element
element.attachEvent(X.Event.getEventName(eventType), wrapper);
// register handler
X.Event.registerHandler(element, eventType, handler, wrapper);
// observe window's unload event to avoid memory leak
var doc = element.ownerDocument || element; // element may be document or window directly
var win = doc.parentWindow || doc;
X.Event._linkUnloadEvent(win);
},
removeListener: function(element, eventType, handler, useCapture)
{
var index = X.Event.findHandlerIndex(element, eventType, handler);
if (index >= 0)
{
var wrapper = X.Event._handlers[eventType][index].wrapper;
element.detachEvent(X.Event.getEventName(eventType), wrapper);
X.Event._handlers[eventType].splice(index, 1); // unregister from list
}
},
stopPropagation: function(event)
{
event.cancelBubble = true;
},
preventDefault: function(event)
{
event.returnValue = false;
},
removeAllListeners: function(doc) // remove all listener in list. If doc is set, only handlers on element in doc will be removed
{
var allHandlers = X.Event._handlers;
if (!allHandlers)
return;
for (var propName in allHandlers)
{
if (allHandlers.hasOwnProperty(propName))
{
var hs = allHandlers[propName];
if (hs && hs.length)
{
var eventName = X.Event.getEventName(propName);
for (var i = hs.length - 1; i >= 0; --i)
{
var h = hs[i];
if ((!doc) || (h.element.ownerDocument === doc))
{
h.element.detachEvent(eventName, h.wrapper);
hs.splice(i, 1);
}
}
}
}
}
},
/** @private */
getEventName: function(eventType)
{
return 'on' + eventType;
},
/** @private */
registerHandler: function(element, eventType, handler, handlerWrapper)
{
var handlers = X.Event._handlers;
if (!handlers[eventType])
handlers[eventType] = [];
var hs = handlers[eventType];
hs.push({
'element': element,
'handler': handler,
'wrapper': handlerWrapper
});
},
/** @private */
unregisterHandler: function(element, eventType, handler)
{
if (!handler[eventType])
return;
var hs = handler[eventType];
var index = X.Event.findHandlerIndex(element, eventType, handler);
if (index >= 0)
hs.splice(index, 1);
},
/** @private */
findHandlerIndex: function(element, eventType, handler)
{
var hs = X.Event._handlers[eventType];
if (!hs)
return -1;
for (var i = 0, l = hs.length; i < l; ++i)
{
var h = hs[i];
if ((h.element === element) && (h.handler === handler))
{
return i;
}
}
return -1;
},
/** @private */
_linkUnloadEvent: function(winObj)
{
var flag = X.Event.WIN_UNLOAD_FLAG_FIELD;
if (winObj[flag]) // already linked
return;
else
{
winObj.attachEvent('onunload', X.Event.reactWinUnload);
winObj[flag] = true;
}
},
/** @private */
reactWinUnload: function()
{
var doc = this.ownerDocument; // this is window object in IE
// detach all handlers to avoid memory leak in IE6
X.Event.removeAllListeners(doc);
}
};
/** @ignore */
X.Event._IEMethods = {
getRelatedTarget: function(event)
{
var etype = event.type;
if (['focusin', 'mouseenter', 'mouseover', 'pointerenter', 'pointerover', 'dragenter'].indexOf(etype) >= 0)
return event.fromElement || event.toElement;
else
return event.toElement || event.fromElement;
},
// methods to get mouse event information
getButton: function(event)
{
// in IE 5-8, button has different values to W3C
switch (event.button)
{
case 1: return 0; // left
case 2: return 2; // right
case 4: return 1; // middle
case 3: return 3; // left and right
default: return event.button;
}
},
stopPropagation: function(event)
{
event.cancelBubble = true;
},
preventDefault: function(event)
{
event.returnValue = false;
}
};
if (document)
{
if (document.addEventListener) // W3C browser
{
X.Event = Object.extend(X.Event, X.Event._W3C);
X.Event.Methods = Object.extend(X.Event.Methods, X.Event._W3CMethods);
if (Kekule.Browser.Gecko) // fix Firefox mousewheel event lacking
{
X.Event = Object.extend(X.Event, X.Event._Gecko);
}
}
else if (document.attachEvent) // IE 8
{
X.Event = Object.extend(X.Event, X.Event._IE);
X.Event.Methods = Object.extend(X.Event.Methods, X.Event._IEMethods);
if (win.Element)
{
var elemPrototype = win.Element.prototype;
elemPrototype.addEventListener = X.Event.addListener.methodize();
elemPrototype.removeEventListener = X.Event.removeListener.methodize();
}
}
}
Object.extend(X.Event, X.Event.Methods);
// insert new methods to Event class
var eproto = null;
if (win.Event)
eproto = win.Event.prototype;
if (!eproto)
{
if (document && document.createEvent)
eproto = document.createEvent('HTMLEvents').__proto__;
}
var hasEventPrototype = !!eproto;
var eventObjMethods = {};
var methods = X.Event.Methods;
for (var name in methods)
{
if (methods.hasOwnProperty(name) && (typeof(methods[name]) === 'function'))
{
eventObjMethods[name] = methods[name].methodize();
}
}
if (eproto) // IE7 can not get event prototype, sucks
{
/*
var methods = X.Event.Methods;
for (name in methods)
{
if (methods.hasOwnProperty(name) && (typeof(methods[name]) === 'function'))
{
if (!eproto[name])
{
eproto[name] = methods[name].methodize();
}
}
}
*/
Object.extend(eproto, eventObjMethods);
};
// enable drag draggable element in IE
(function(){
if (typeof(document) !== 'undefined')
{
var div = document.createElement('div');
var needPolyfill = !('draggable' in div) && ('ondragstart' in div && 'ondrop' in div);
//var needPolyfill = !!Kekule.Browser.IE;
if (needPolyfill)
{
Kekule.X.Event.addListener(document, 'selectstart', function(e){
for (var el = e.target; el; el = el.parentNode) {
if (el.attributes && el.attributes['draggable']) {
e.preventDefault();
if (e.stopImmediatePropagation)
e.stopImmediatePropagation();
else
e.stopPropagation();
el.dragDrop();
return false;
}
}
});
}
}
})();
/////////////////////////////////////////////////////////////
// Cross browser AJAX supporting
/////////////////////////////////////////////////////////////
/**
* Support of cross browser AJAX request.
* Code borrowed from http://www.quirksmode.org/js/xmlhttp.html.
*
* User can do AJAX call by something like:
* Kekule.X.Ajax.sendRequest('file.txt', function(req, success)
* {
* if (success)
* alert(req.responseText);
* }
* );
*
* @class
*/
X.Ajax = {
/** @private */
XMLHttpFactories: [
function () {return new XMLHttpRequest()},
function () {return new ActiveXObject("Msxml2.XMLHTTP")},
function () {return new ActiveXObject("Msxml3.XMLHTTP")},
function () {return new ActiveXObject("Microsoft.XMLHTTP")}
],
/** @private */
_availableFactoryIndex: -1,
/** @private */
createXMLHTTPObject: function()
{
var xmlhttp = false;
var F = X.Ajax.XMLHttpFactories;
var index = X.Ajax._availableFactoryIndex;
if (index >= 0)
xmlhttp = F[index]();
else
{
for (var i = 0; i < F.length;i++) {
try {
xmlhttp = F[i]();
X.Ajax._availableFactoryIndex = i;
}
catch (e) {
continue;
}
break;
}
}
return xmlhttp;
},
preparePostData: function(data)
{
var result = data;
if (typeof(data) === 'object') // HASH
{
result = Kekule.UrlUtils.generateSearchString(data);
}
return result;
},
/**
* Send an AJAX request to URL.
* @param {Hash} params The request params, may including the following fields:
* {
* callback: Call back function with params (data, requestObj, success),
* url: string,
* postData: Hash or string,
* xhrProps: Hash, properties that overriding the default ones of XMLHttpRequest object.
* e.g. {responseType, overwriteMimeType, withCredentials}.
* }
* @returns {XMLHttpRequest}
*/
request: function(params)
{
return X.Ajax.sendRequest(params.url, params.callback, params.postData, params.xhrProps);
},
/**
* Send an AJAX request to URL.
* This method is deprecated, since the input paramters are not well organized.
* Now user should use method {@link Kekule.X.Ajax.request} instead.
* @param {String} url
* @param {Function} callback Call back function with params (data, requestObj, success)
* @param {Array} postData Optional.
* //@param {String} responseType Value of responseType property of XMLHttpRequest(V2).
* //@param {String} overwriteMimeType Value to call overwriteMimeType method of XMLHttpRequest(V2).
* @param {Hash} xhrProps Property settings of XHR object, e.g. {withCredentials: true}.
* @deprecated
*/
sendRequest: function(url, callback, postData, responseTypeOrXhrProps, overwriteMimeType)
{
var isBinary = false;
var supportResponseType = true;
var req = X.Ajax.createXMLHTTPObject();
if (!req) return;
var method = (postData) ? "POST" : "GET";
req.open(method, url, true);
req.setRequestHeader('User-Agent','XMLHTTP/1.0');
if (postData)
req.setRequestHeader('Content-type','application/x-www-form-urlencoded');
var responseType, xhrProps;
if (responseTypeOrXhrProps) // for backward compatible, here we check if the fourth param is a hash(xhrProps) or string (responseType)
{
if (typeof(responseTypeOrXhrProps) === 'object')
{
xhrProps = responseTypeOrXhrProps;
responseType = xhrProps.responseType;
overwriteMimeType = xhrProps.overwriteMimeType; // overwriteMimeType is also set in xhrProps
}
else if (typeof(responseTypeOrXhrProps) === 'string')
responseType = responseTypeOrXhrProps;
}
if (responseType)
{
try
{
req.responseType = responseType;
}
catch(e)
{
}
if ((responseType === 'arraybuffer') || (responseType === 'blob'))
isBinary = true;
if (req.responseType !== responseType) // old fashion browser, do not support this feature
supportResponseType = false;
}
if (isBinary && (!supportResponseType) && req.overwriteMimeType)
req.overrideMimeType('text/plain; charset=x-user-defined'); // old browser, need to transform binary data by string
if (overwriteMimeType && req.overwriteMimeType)
req.overwriteMimeType(overwriteMimeType);
if (xhrProps)
{
for (var key in xhrProps)
{
if (key in req)
{
try
{
req[key] = xhrProps[key];
}
catch(e)
{
}
}
}
}
req.onreadystatechange = function ()
{
if (req.readyState != 4) return;
if (req.status != 200 && req.status != 304)
{
callback(null, req, false);
throw 'HTTP error ' + req.status;
return;
}
//console.log(req);
// TODO: need to handle old fashion binary handling here
callback(req.response || req.responseText, req, true);
};
if (req.readyState == 4) return;
req.send(X.Ajax.preparePostData(postData));
return req;
},
/**
* Retrieve response MIME type of an AJAX request object.
* @param {Object} request
*/
getResponseMimeType: function(request)
{
return request.getResponseHeader("content-type") || "";
}
};
/**
* Short cut of {@link Kekule.X.Ajax}.
* @class
*/
Kekule.Ajax = Kekule.X.Ajax;
/////////////////////////////////////////////////////////////
// Cross browser DOM ready event supporting
/////////////////////////////////////////////////////////////
// DOM ready support, the solution is copy from
// http://www.cnblogs.com/rubylouvre/archive/2009/12/30/1635645.html
/** @ignore */
Kekule.X.DomReady = {
isReady: false,
suspendFlag: 0,
funcs: [],
domReady: function(fn, doc)
{
if (!doc)
doc = document;
DOM.initReady(doc);//如果没有建成DOM树,则走第二步,存储起来一起杀
if (!DOM.isReady)
{
var readyState = doc && doc.readyState;
if (readyState === 'complete' || readyState === 'loaded' // document already loaded, call fn directly
|| (readyState === 'interactive' && !Kekule.Browser.IE))
{
DOM.isReady = true;
}
//console.log('[document ready state]', readyState, fn, fn.toString());
}
if (DOM.isReady)
{
fn();//如果已经建成DOM,则来一个杀一个
}
else
{
DOM.funcs.push(fn);//存储加载事件
}
},
fireReady: function()
{
if (DOM.isReady)
return;
DOM.isReady = true;
/*
for (var i = 0, l = DOM.funcs.length; i < l; ++i)
{
var fn = DOM.funcs[i];
fn();
}
DOM.funcs.length = 0;//清空事件
*/
DOM._execFuncs();
},
_execFuncs: function()
{
var funcs = DOM.funcs;
if (funcs && funcs.length)
{
while (funcs.length && !DOM.isSuspending())
{
var fn = funcs.shift();
if (fn)
fn();
}
}
},
suspend: function()
{
++DOM.suspendFlag;
},
resume: function()
{
if (DOM.suspendFlag > 0)
--DOM.suspendFlag;
if (!DOM.isSuspending())
{
DOM._execFuncs();
}
},
isSuspending: function()
{
return DOM.suspendFlag > 0;
},
initReady: function initReady(doc)
{
if (!doc)
doc = document;
if (doc && doc.addEventListener) {
doc.addEventListener( "DOMContentLoaded", function(){
doc.removeEventListener( "DOMContentLoaded", initReady /*arguments.callee*/, false );//清除加载函数
DOM.fireReady();
}, false);
}
else
{
if (doc && doc.getElementById) {
doc.write('<script id="ie-domReady" defer="defer" src="\//:"><\/script>');
doc.getElementById("ie-domReady").onreadystatechange = function() {
if (this.readyState === "complete") {
DOM.fireReady();
this.onreadystatechange = null;
this.parentNode.removeChild(this)
}
};
}
}
}
};
/** @ignore */
var DOM = Kekule.X.DomReady;
/**
* Invoke when page DOM is loaded.
* @param {Func} fn Callback function.
* @function
*/
Kekule.X.domReady = DOM.domReady;
})(this);