ol-ext
Version:
A set of cool extensions for OpenLayers (ol) in node modules structure
619 lines (582 loc) • 19 kB
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