Files
nodeMap/MessagesMap/Scripts/utilities.js
2024-04-27 15:47:36 +02:00

272 lines
7.7 KiB
JavaScript

module.exports = {
/**
* Extends an object
*
* @param {Object} target object to extend
* @param {Object} source object to take properties from
* @return {Object} extended object
*/
extend: function(target, source) {
target = target || {};
for (var prop in source) {
// Go recursively
if (this.isObject(source[prop])) {
target[prop] = this.extend(target[prop], source[prop])
} else {
target[prop] = source[prop]
}
}
return target;
}
/**
* Checks if an object is a DOM element
*
* @param {Object} o HTML element or String
* @return {Boolean} returns true if object is a DOM element
*/
, isElement: function(o){
return (
o instanceof HTMLElement || o instanceof SVGElement || o instanceof SVGSVGElement || //DOM2
(o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string')
);
}
/**
* Checks if an object is an Object
*
* @param {Object} o Object
* @return {Boolean} returns true if object is an Object
*/
, isObject: function(o){
return Object.prototype.toString.call(o) === '[object Object]';
}
/**
* Checks if variable is Number
*
* @param {Integer|Float} n
* @return {Boolean} returns true if variable is Number
*/
, isNumber: function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
/**
* Search for an SVG element
*
* @param {Object|String} elementOrSelector DOM Element or selector String
* @return {Object|Null} SVG or null
*/
, getSvg: function(elementOrSelector) {
var element
, svg;
if (!this.isElement(elementOrSelector)) {
// If selector provided
if (typeof elementOrSelector === 'string' || elementOrSelector instanceof String) {
// Try to find the element
element = document.querySelector(elementOrSelector)
if (!element) {
throw new Error('Provided selector did not find any elements. Selector: ' + elementOrSelector)
return null
}
} else {
throw new Error('Provided selector is not an HTML object nor String')
return null
}
} else {
element = elementOrSelector
}
if (element.tagName.toLowerCase() === 'svg') {
svg = element;
} else {
if (element.tagName.toLowerCase() === 'object') {
svg = element.contentDocument.documentElement;
} else {
if (element.tagName.toLowerCase() === 'embed') {
svg = element.getSVGDocument().documentElement;
} else {
if (element.tagName.toLowerCase() === 'img') {
throw new Error('Cannot script an SVG in an "img" element. Please use an "object" element or an in-line SVG.');
} else {
throw new Error('Cannot get SVG.');
}
return null
}
}
}
return svg
}
/**
* Attach a given context to a function
* @param {Function} fn Function
* @param {Object} context Context
* @return {Function} Function with certain context
*/
, proxy: function(fn, context) {
return function() {
return fn.apply(context, arguments)
}
}
/**
* Returns object type
* Uses toString that returns [object SVGPoint]
* And than parses object type from string
*
* @param {Object} o Any object
* @return {String} Object type
*/
, getType: function(o) {
return Object.prototype.toString.apply(o).replace(/^\[object\s/, '').replace(/\]$/, '')
}
/**
* If it is a touch event than add clientX and clientY to event object
*
* @param {Event} evt
* @param {SVGSVGElement} svg
*/
, mouseAndTouchNormalize: function(evt, svg) {
// If no clientX then fallback
if (evt.clientX === void 0 || evt.clientX === null) {
// Fallback
evt.clientX = 0
evt.clientY = 0
// If it is a touch event
if (evt.touches !== void 0 && evt.touches.length) {
if (evt.touches[0].clientX !== void 0) {
evt.clientX = evt.touches[0].clientX
evt.clientY = evt.touches[0].clientY
} else if (evt.touches[0].pageX !== void 0) {
var rect = svg.getBoundingClientRect();
evt.clientX = evt.touches[0].pageX - rect.left
evt.clientY = evt.touches[0].pageY - rect.top
}
// If it is a custom event
} else if (evt.originalEvent !== void 0) {
if (evt.originalEvent.clientX !== void 0) {
evt.clientX = evt.originalEvent.clientX
evt.clientY = evt.originalEvent.clientY
}
}
}
}
/**
* Check if an event is a double click/tap
* TODO: For touch gestures use a library (hammer.js) that takes in account other events
* (touchmove and touchend). It should take in account tap duration and traveled distance
*
* @param {Event} evt
* @param {Event} prevEvt Previous Event
* @return {Boolean}
*/
, isDblClick: function(evt, prevEvt) {
// Double click detected by browser
if (evt.detail === 2) {
return true;
}
// Try to compare events
else if (prevEvt !== void 0 && prevEvt !== null) {
var timeStampDiff = evt.timeStamp - prevEvt.timeStamp // should be lower than 250 ms
, touchesDistance = Math.sqrt(Math.pow(evt.clientX - prevEvt.clientX, 2) + Math.pow(evt.clientY - prevEvt.clientY, 2))
return timeStampDiff < 250 && touchesDistance < 10
}
// Nothing found
return false;
}
/**
* Returns current timestamp as an integer
*
* @return {Number}
*/
, now: Date.now || function() {
return new Date().getTime();
}
// From underscore.
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
// jscs:disable
// jshint ignore:start
, throttle: function(func, wait, options) {
var that = this;
var context, args, result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : that.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
var now = that.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
}
// jshint ignore:end
// jscs:enable
/**
* Create a requestAnimationFrame simulation
*
* @param {Number|String} refreshRate
* @return {Function}
*/
, createRequestAnimationFrame: function(refreshRate) {
var timeout = null
// Convert refreshRate to timeout
if (refreshRate !== 'auto' && refreshRate < 60 && refreshRate > 1) {
timeout = Math.floor(1000 / refreshRate)
}
if (timeout === null) {
return window.requestAnimationFrame || requestTimeout(33)
} else {
return requestTimeout(timeout)
}
}
}
/**
* Create a callback that will execute after a given timeout
*
* @param {Function} timeout
* @return {Function}
*/
function requestTimeout(timeout) {
return function(callback) {
window.setTimeout(callback, timeout)
}
}