619 lines
18 KiB
JavaScript
619 lines
18 KiB
JavaScript
/** Vanilla JS helper to manipulate DOM without jQuery
|
|
* @see https://github.com/nefe/You-Dont-Need-jQuery
|
|
* @see https://plainjs.com/javascript/
|
|
* @see http://youmightnotneedjquery.com/
|
|
*/
|
|
import ol_Map from 'ol/Map.js'
|
|
import ol_ext_input_Checkbox from './input/Checkbox.js'
|
|
import ol_ext_input_Switch from './input/Switch.js'
|
|
import ol_ext_input_Radio from './input/Radio.js'
|
|
|
|
/** @namespace ol.ext.element */
|
|
var ol_ext_element = {};
|
|
|
|
/**
|
|
* Create an element
|
|
* @param {string} tagName The element tag, use 'TEXT' to create a text node
|
|
* @param {*} options
|
|
* @param {string} options.className className The element class name
|
|
* @param {Element} options.parent Parent to append the element as child
|
|
* @param {Element|string} [options.html] Content of the element (if text is not set)
|
|
* @param {string} [options.text] Text content (if html is not set)
|
|
* @param {Element|string} [options.options] when tagName = SELECT a list of options as key:value to add to the select
|
|
* @param {string} options.* Any other attribut to add to the element
|
|
*/
|
|
ol_ext_element.create = function (tagName, options) {
|
|
options = options || {};
|
|
var elt;
|
|
// Create text node
|
|
if (tagName === 'TEXT') {
|
|
elt = document.createTextNode(options.html||'');
|
|
if (options.parent) options.parent.appendChild(elt);
|
|
} else {
|
|
// Other element
|
|
elt = document.createElement(tagName.toLowerCase());
|
|
if (/button/i.test(tagName)) elt.setAttribute('type', 'button');
|
|
for (var attr in options) {
|
|
switch (attr) {
|
|
case 'className': {
|
|
if (options.className && options.className.trim) elt.setAttribute('class', options.className.trim());
|
|
break;
|
|
}
|
|
case 'text': {
|
|
elt.innerText = options.text;
|
|
break;
|
|
}
|
|
case 'html': {
|
|
if (options.html instanceof Element) elt.appendChild(options.html)
|
|
else if (options.html!==undefined) elt.innerHTML = options.html;
|
|
break;
|
|
}
|
|
case 'parent': {
|
|
if (options.parent) options.parent.appendChild(elt);
|
|
break;
|
|
}
|
|
case 'options': {
|
|
if (/select/i.test(tagName)) {
|
|
for (var i in options.options) {
|
|
ol_ext_element.create('OPTION', {
|
|
html: i,
|
|
value: options.options[i],
|
|
parent: elt
|
|
})
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 'style': {
|
|
ol_ext_element.setStyle(elt, options.style);
|
|
break;
|
|
}
|
|
case 'change':
|
|
case 'click': {
|
|
ol_ext_element.addListener(elt, attr, options[attr]);
|
|
break;
|
|
}
|
|
case 'on': {
|
|
for (var e in options.on) {
|
|
ol_ext_element.addListener(elt, e, options.on[e]);
|
|
}
|
|
break;
|
|
}
|
|
case 'checked': {
|
|
elt.checked = !!options.checked;
|
|
break;
|
|
}
|
|
default: {
|
|
elt.setAttribute(attr, options[attr]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return elt;
|
|
};
|
|
|
|
/** Create a toggle switch input
|
|
* @param {*} options
|
|
* @param {string|Element} options.html
|
|
* @param {string|Element} options.after
|
|
* @param {boolean} options.checked
|
|
* @param {*} [options.on] a list of actions
|
|
* @param {function} [options.click]
|
|
* @param {function} [options.change]
|
|
* @param {Element} options.parent
|
|
*/
|
|
ol_ext_element.createSwitch = function (options) {
|
|
var input = ol_ext_element.create('INPUT', {
|
|
type: 'checkbox',
|
|
on: options.on,
|
|
click: options.click,
|
|
change: options.change,
|
|
parent: options.parent
|
|
});
|
|
var opt = Object.assign ({ input: input }, options || {});
|
|
new ol_ext_input_Switch(opt);
|
|
return input;
|
|
};
|
|
|
|
/** Create a toggle switch input
|
|
* @param {*} options
|
|
* @param {string|Element} options.html
|
|
* @param {string|Element} options.after
|
|
* @param {string} [options.name] input name
|
|
* @param {string} [options.type=checkbox] input type: radio or checkbox
|
|
* @param {string} options.value input value
|
|
* @param {*} [options.on] a list of actions
|
|
* @param {function} [options.click]
|
|
* @param {function} [options.change]
|
|
* @param {Element} options.parent
|
|
*/
|
|
ol_ext_element.createCheck = function (options) {
|
|
var input = ol_ext_element.create('INPUT', {
|
|
name: options.name,
|
|
type: (options.type==='radio' ? 'radio' : 'checkbox'),
|
|
on: options.on,
|
|
parent: options.parent
|
|
});
|
|
var opt = Object.assign ({ input: input }, options || {});
|
|
if (options.type === 'radio') {
|
|
new ol_ext_input_Radio(opt);
|
|
} else {
|
|
new ol_ext_input_Checkbox(opt);
|
|
}
|
|
return input;
|
|
};
|
|
|
|
/** Set inner html or append a child element to an element
|
|
* @param {Element} element
|
|
* @param {Element|string} html Content of the element
|
|
*/
|
|
ol_ext_element.setHTML = function(element, html) {
|
|
if (html instanceof Element) element.appendChild(html)
|
|
else if (html!==undefined) element.innerHTML = html;
|
|
};
|
|
|
|
/** Append text into an elemnt
|
|
* @param {Element} element
|
|
* @param {string} text text content
|
|
*/
|
|
ol_ext_element.appendText = function(element, text) {
|
|
element.appendChild(document.createTextNode(text||''));
|
|
};
|
|
|
|
/**
|
|
* Add a set of event listener to an element
|
|
* @param {Element} element
|
|
* @param {string|Array<string>} eventType
|
|
* @param {function} fn
|
|
*/
|
|
ol_ext_element.addListener = function (element, eventType, fn, useCapture ) {
|
|
if (typeof eventType === 'string') eventType = eventType.split(' ');
|
|
eventType.forEach(function(e) {
|
|
element.addEventListener(e, fn, useCapture);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Add a set of event listener to an element
|
|
* @param {Element} element
|
|
* @param {string|Array<string>} eventType
|
|
* @param {function} fn
|
|
*/
|
|
ol_ext_element.removeListener = function (element, eventType, fn) {
|
|
if (typeof eventType === 'string') eventType = eventType.split(' ');
|
|
eventType.forEach(function(e) {
|
|
element.removeEventListener(e, fn);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Show an element
|
|
* @param {Element} element
|
|
*/
|
|
ol_ext_element.show = function (element) {
|
|
element.style.display = '';
|
|
};
|
|
|
|
/**
|
|
* Hide an element
|
|
* @param {Element} element
|
|
*/
|
|
ol_ext_element.hide = function (element) {
|
|
element.style.display = 'none';
|
|
};
|
|
|
|
/**
|
|
* Test if an element is hihdden
|
|
* @param {Element} element
|
|
* @return {boolean}
|
|
*/
|
|
ol_ext_element.hidden = function (element) {
|
|
return ol_ext_element.getStyle(element, 'display') === 'none';
|
|
};
|
|
|
|
/**
|
|
* Toggle an element
|
|
* @param {Element} element
|
|
*/
|
|
ol_ext_element.toggle = function (element) {
|
|
element.style.display = (element.style.display==='none' ? '' : 'none');
|
|
};
|
|
|
|
/** Set style of an element
|
|
* @param {DOMElement} el the element
|
|
* @param {*} st list of style
|
|
*/
|
|
ol_ext_element.setStyle = function(el, st) {
|
|
for (var s in st) {
|
|
switch (s) {
|
|
case 'top':
|
|
case 'left':
|
|
case 'bottom':
|
|
case 'right':
|
|
case 'minWidth':
|
|
case 'maxWidth':
|
|
case 'width':
|
|
case 'height': {
|
|
if (typeof(st[s]) === 'number') {
|
|
el.style[s] = st[s]+'px';
|
|
} else {
|
|
el.style[s] = st[s];
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
el.style[s] = st[s];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get style propertie of an element
|
|
* @param {DOMElement} el the element
|
|
* @param {string} styleProp Propertie name
|
|
* @return {*} style value
|
|
*/
|
|
ol_ext_element.getStyle = function(el, styleProp) {
|
|
var value, defaultView = (el.ownerDocument || document).defaultView;
|
|
// W3C standard way:
|
|
if (defaultView && defaultView.getComputedStyle) {
|
|
// sanitize property name to css notation
|
|
// (hypen separated words eg. font-Size)
|
|
styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
value = defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);
|
|
} else if (el.currentStyle) { // IE
|
|
// sanitize property name to camelCase
|
|
styleProp = styleProp.replace(/-(\w)/g, function(str, letter) {
|
|
return letter.toUpperCase();
|
|
});
|
|
value = el.currentStyle[styleProp];
|
|
// convert other units to pixels on IE
|
|
if (/^\d+(em|pt|%|ex)?$/i.test(value)) {
|
|
return (function(value) {
|
|
var oldLeft = el.style.left, oldRsLeft = el.runtimeStyle.left;
|
|
el.runtimeStyle.left = el.currentStyle.left;
|
|
el.style.left = value || 0;
|
|
value = el.style.pixelLeft + "px";
|
|
el.style.left = oldLeft;
|
|
el.runtimeStyle.left = oldRsLeft;
|
|
return value;
|
|
})(value);
|
|
}
|
|
}
|
|
if (/px$/.test(value)) return parseInt(value);
|
|
return value;
|
|
};
|
|
|
|
/** Get outerHeight of an elemen
|
|
* @param {DOMElement} elt
|
|
* @return {number}
|
|
*/
|
|
ol_ext_element.outerHeight = function(elt) {
|
|
return elt.offsetHeight + ol_ext_element.getStyle(elt, 'marginBottom')
|
|
};
|
|
|
|
/** Get outerWidth of an elemen
|
|
* @param {DOMElement} elt
|
|
* @return {number}
|
|
*/
|
|
ol_ext_element.outerWidth = function(elt) {
|
|
return elt.offsetWidth + ol_ext_element.getStyle(elt, 'marginLeft')
|
|
};
|
|
|
|
/** Get element offset rect
|
|
* @param {DOMElement} elt
|
|
* @return {*}
|
|
*/
|
|
ol_ext_element.offsetRect = function(elt) {
|
|
var rect = elt.getBoundingClientRect();
|
|
return {
|
|
top: rect.top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0),
|
|
left: rect.left + (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0),
|
|
height: rect.height || (rect.bottom - rect.top),
|
|
width: rect.width || (rect.right - rect.left)
|
|
}
|
|
};
|
|
|
|
/** Get element offset
|
|
* @param {ELement} elt
|
|
* @returns {Object} top/left offset
|
|
*/
|
|
ol_ext_element.getFixedOffset = function(elt) {
|
|
var offset = {
|
|
left:0,
|
|
top:0
|
|
};
|
|
var getOffset = function(parent) {
|
|
if (!parent) return offset;
|
|
// Check position when transform
|
|
if (ol_ext_element.getStyle(parent, 'position') === 'absolute'
|
|
&& ol_ext_element.getStyle(parent, 'transform') !== "none") {
|
|
var r = parent.getBoundingClientRect();
|
|
offset.left += r.left;
|
|
offset.top += r.top;
|
|
return offset;
|
|
}
|
|
return getOffset(parent.offsetParent)
|
|
}
|
|
return getOffset(elt.offsetParent)
|
|
};
|
|
|
|
/** Get element offset rect
|
|
* @param {DOMElement} elt
|
|
* @param {boolean} fixed get fixed position
|
|
* @return {Object}
|
|
*/
|
|
ol_ext_element.positionRect = function(elt, fixed) {
|
|
var gleft = 0;
|
|
var gtop = 0;
|
|
|
|
var getRect = function( parent ) {
|
|
if (parent) {
|
|
gleft += parent.offsetLeft;
|
|
gtop += parent.offsetTop;
|
|
return getRect(parent.offsetParent);
|
|
} else {
|
|
var r = {
|
|
top: elt.offsetTop + gtop,
|
|
left: elt.offsetLeft + gleft
|
|
};
|
|
if (fixed) {
|
|
r.top -= (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0);
|
|
r.left -= (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0);
|
|
}
|
|
r.bottom = r.top + elt.offsetHeight;
|
|
r.right = r.top + elt.offsetWidth;
|
|
return r;
|
|
}
|
|
};
|
|
return getRect(elt.offsetParent);
|
|
}
|
|
|
|
/** Make a div scrollable without scrollbar.
|
|
* On touch devices the default behavior is preserved
|
|
* @param {DOMElement} elt
|
|
* @param {*} options
|
|
* @param {function} [options.onmove] a function that takes a boolean indicating that the div is scrolling
|
|
* @param {boolean} [options.vertical=false]
|
|
* @param {boolean} [options.animate=true] add kinetic to scroll
|
|
* @param {boolean} [options.mousewheel=false] enable mousewheel to scroll
|
|
* @param {boolean} [options.minibar=false] add a mini scrollbar to the parent element (only vertical scrolling)
|
|
* @returns {Object} an object with a refresh function
|
|
*/
|
|
ol_ext_element.scrollDiv = function(elt, options) {
|
|
options = options || {};
|
|
var pos = false;
|
|
var speed = 0;
|
|
var d, dt = 0;
|
|
|
|
var onmove = (typeof(options.onmove) === 'function' ? options.onmove : function(){});
|
|
//var page = options.vertical ? 'pageY' : 'pageX';
|
|
var page = options.vertical ? 'screenY' : 'screenX';
|
|
var scroll = options.vertical ? 'scrollTop' : 'scrollLeft';
|
|
var moving = false;
|
|
// Factor scale content / container
|
|
var scale, isbar;
|
|
|
|
// Update the minibar
|
|
var updateCounter = 0;
|
|
var updateMinibar = function() {
|
|
if (scrollbar) {
|
|
updateCounter++;
|
|
setTimeout(updateMinibarDelay);
|
|
}
|
|
}
|
|
var updateMinibarDelay = function() {
|
|
if (scrollbar) {
|
|
updateCounter--;
|
|
// Prevent multi call
|
|
if (updateCounter) return;
|
|
// Container height
|
|
var pheight = elt.clientHeight;
|
|
// Content height
|
|
var height = elt.scrollHeight;
|
|
// Set scrollbar value
|
|
scale = pheight / height;
|
|
scrollbar.style.height = scale * 100 + '%';
|
|
scrollbar.style.top = (elt.scrollTop / height * 100) + '%';
|
|
scrollContainer.style.height = pheight + 'px';
|
|
// No scroll
|
|
if (pheight > height - .5) scrollContainer.classList.add('ol-100pc');
|
|
else scrollContainer.classList.remove('ol-100pc');
|
|
}
|
|
}
|
|
|
|
// Handle pointer down
|
|
var onPointerDown = function(e) {
|
|
// Prevent scroll
|
|
if (e.target.classList.contains('ol-noscroll')) return;
|
|
// Start scrolling
|
|
moving = false;
|
|
pos = e[page];
|
|
dt = new Date();
|
|
elt.classList.add('ol-move');
|
|
// Prevent elt dragging
|
|
e.preventDefault();
|
|
// Listen scroll
|
|
window.addEventListener('pointermove', onPointerMove);
|
|
ol_ext_element.addListener(window, ['pointerup','pointercancel'], onPointerUp);
|
|
}
|
|
|
|
// Register scroll
|
|
var onPointerMove = function(e) {
|
|
if (pos !== false) {
|
|
var delta = (isbar ? -1/scale : 1) * (pos - e[page]);
|
|
moving = moving || Math.round(delta)
|
|
elt[scroll] += delta;
|
|
d = new Date();
|
|
if (d-dt) {
|
|
speed = (speed + delta / (d - dt))/2;
|
|
}
|
|
pos = e[page];
|
|
dt = d;
|
|
// Tell we are moving
|
|
if (delta) onmove(true);
|
|
} else {
|
|
moving = true;
|
|
}
|
|
};
|
|
|
|
// Animate scroll
|
|
var animate = function(to) {
|
|
var step = (to>0) ? Math.min(100, to/2) : Math.max(-100, to/2);
|
|
to -= step;
|
|
elt[scroll] += step;
|
|
if (-1 < to && to < 1) {
|
|
if (moving) setTimeout(function() { elt.classList.remove('ol-move'); });
|
|
else elt.classList.remove('ol-move');
|
|
moving = false;
|
|
onmove(false);
|
|
} else {
|
|
setTimeout(function() {
|
|
animate(to);
|
|
}, 40);
|
|
}
|
|
}
|
|
|
|
// Initialize scroll container for minibar
|
|
var scrollContainer, scrollbar;
|
|
if (options.vertical && options.minibar) {
|
|
var init = function(b) {
|
|
// only once
|
|
elt.removeEventListener('pointermove', init);
|
|
elt.parentNode.classList.add('ol-miniscroll');
|
|
scrollbar = ol_ext_element.create('DIV');
|
|
scrollContainer = ol_ext_element.create('DIV', {
|
|
className: 'ol-scroll',
|
|
html: scrollbar
|
|
});
|
|
elt.parentNode.insertBefore(scrollContainer, elt);
|
|
// Move scrollbar
|
|
scrollbar.addEventListener('pointerdown', function(e) {
|
|
isbar = true;
|
|
onPointerDown(e)
|
|
});
|
|
// Handle mousewheel
|
|
if (options.mousewheel) {
|
|
ol_ext_element.addListener(scrollContainer,
|
|
['mousewheel', 'DOMMouseScroll', 'onmousewheel'],
|
|
function(e) { onMouseWheel(e) }
|
|
);
|
|
ol_ext_element.addListener(scrollbar,
|
|
['mousewheel', 'DOMMouseScroll', 'onmousewheel'],
|
|
function(e) { onMouseWheel(e) }
|
|
);
|
|
}
|
|
// Update on enter
|
|
elt.parentNode.addEventListener('pointerenter', updateMinibar);
|
|
// Update on resize
|
|
window.addEventListener('resize', updateMinibar);
|
|
// Update
|
|
if (b!==false) updateMinibar();
|
|
};
|
|
// Allready inserted in the DOM
|
|
if (elt.parentNode) init(false);
|
|
// or wait when ready
|
|
else elt.addEventListener('pointermove', init);
|
|
// Update on scroll
|
|
elt.addEventListener('scroll', function() {
|
|
updateMinibar();
|
|
});
|
|
}
|
|
|
|
// Enable scroll
|
|
elt.style['touch-action'] = 'none';
|
|
elt.style['overflow'] = 'hidden';
|
|
elt.classList.add('ol-scrolldiv');
|
|
|
|
// Start scrolling
|
|
ol_ext_element.addListener(elt, ['pointerdown'], function(e) {
|
|
isbar = false;
|
|
onPointerDown(e)
|
|
});
|
|
|
|
// Prevet click when moving...
|
|
elt.addEventListener('click', function(e) {
|
|
if (elt.classList.contains('ol-move')) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
}, true);
|
|
|
|
// Stop scrolling
|
|
var onPointerUp = function(e) {
|
|
dt = new Date() - dt;
|
|
if (dt>100 || isbar) {
|
|
// User stop: no speed
|
|
speed = 0;
|
|
} else if (dt>0) {
|
|
// Calculate new speed
|
|
speed = ((speed||0) + (pos - e[page]) / dt) / 2;
|
|
}
|
|
animate(options.animate===false ? 0 : speed*200);
|
|
pos = false;
|
|
speed = 0;
|
|
dt = 0;
|
|
// Add class to handle click (on iframe / double-click)
|
|
if (!elt.classList.contains('ol-move')) {
|
|
elt.classList.add('ol-hasClick')
|
|
setTimeout(function() { elt.classList.remove('ol-hasClick'); }, 500);
|
|
} else {
|
|
elt.classList.remove('ol-hasClick');
|
|
}
|
|
isbar = false;
|
|
window.removeEventListener('pointermove', onPointerMove)
|
|
ol_ext_element.removeListener(window, ['pointerup','pointercancel'], onPointerUp);
|
|
};
|
|
|
|
// Handle mousewheel
|
|
var onMouseWheel = function(e) {
|
|
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
|
|
elt.classList.add('ol-move');
|
|
elt[scroll] -= delta*30;
|
|
elt.classList.remove('ol-move');
|
|
return false;
|
|
}
|
|
if (options.mousewheel) { // && !elt.classList.contains('ol-touch')) {
|
|
ol_ext_element.addListener(elt,
|
|
['mousewheel', 'DOMMouseScroll', 'onmousewheel'],
|
|
onMouseWheel
|
|
);
|
|
}
|
|
|
|
return {
|
|
refresh: updateMinibar
|
|
}
|
|
};
|
|
|
|
/** Dispatch an event to an Element
|
|
* @param {string} eventName
|
|
* @param {Element} element
|
|
*/
|
|
ol_ext_element.dispatchEvent = function (eventName, element) {
|
|
var event;
|
|
try {
|
|
event = new CustomEvent(eventName);
|
|
} catch(e) {
|
|
// Try customevent on IE
|
|
event = document.createEvent("CustomEvent");
|
|
event.initCustomEvent(eventName, true, true, {});
|
|
}
|
|
element.dispatchEvent(event);
|
|
};
|
|
|
|
/** Set cursor
|
|
* @param {Element|ol/Map} elt
|
|
* @param {string} cursor
|
|
*/
|
|
ol_ext_element.setCursor = function(elt, cursor) {
|
|
if (elt instanceof ol_Map) elt = elt.getTargetElement()
|
|
// prevent flashing on mobile device
|
|
if (!('ontouchstart' in window) && elt instanceof Element) {
|
|
elt.style.cursor = cursor;
|
|
}
|
|
}
|
|
|
|
export default ol_ext_element
|