Choices/src/scripts/lib/utils.js

397 lines
10 KiB
JavaScript
Raw Normal View History

/**
* Generates a string of random chars
* @param {Number} length Length of the string to generate
* @return {String} String of random chars
*/
2017-08-16 10:47:22 +02:00
export const generateChars = function(length) {
let chars = '';
2017-08-16 10:47:22 +02:00
for (let i = 0; i < length; i++) {
const randomChar = getRandomNumber(0, 36);
chars += randomChar.toString(36);
}
return chars;
};
/**
* Generates a unique id based on an element
* @param {HTMLElement} element Element to generate the id from
* @param {String} Prefix for the Id
* @return {String} Unique Id
*/
2017-08-16 10:47:22 +02:00
export const generateId = function(element, prefix) {
2018-05-28 16:50:16 +02:00
let id =
element.id ||
(element.name && `${element.name}-${generateChars(2)}`) ||
generateChars(4);
id = id.replace(/(:|\.|\[|\]|,)/g, '');
id = prefix + id;
return id;
};
2017-02-17 10:23:52 +01:00
/**
* Tests the type of an object
* @param {String} type Type to test object against
* @param {Object} obj Object to be tested
* @return {Boolean}
*/
export const getType = function(obj) {
return Object.prototype.toString.call(obj).slice(8, -1);
};
2016-03-16 21:24:11 +01:00
/**
* Tests the type of an object
* @param {String} type Type to test object against
* @param {Object} obj Object to be tested
* @return {Boolean}
*/
export const isType = function(type, obj) {
2017-08-16 10:47:22 +02:00
const clas = getType(obj);
return obj !== undefined && obj !== null && clas === type;
2016-03-16 21:24:11 +01:00
};
/**
* Tests to see if a passed object is an element
* @param {Object} obj Object to be tested
* @return {Boolean}
*/
2018-05-28 16:50:16 +02:00
export const isElement = o =>
typeof HTMLElement === 'object'
? o instanceof HTMLElement // DOM2
: o &&
typeof o === 'object' &&
o !== null &&
o.nodeType === 1 &&
typeof o.nodeName === 'string';
2016-04-04 22:44:32 +02:00
/**
* Merges unspecified amount of objects into new object
* @private
* @return {Object} Merged object of arguments
*/
export const extend = function() {
2017-08-16 10:47:22 +02:00
const extended = {};
const length = arguments.length;
/**
* Merge one object into another
* @param {Object} obj Object to merge into extended object
*/
2017-08-16 10:47:22 +02:00
const merge = function(obj) {
for (const prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// If deep merge and property is an object, merge properties
2018-05-28 16:50:16 +02:00
if (isType('Object', obj[prop])) {
extended[prop] = extend(true, extended[prop], obj[prop]);
} else {
extended[prop] = obj[prop];
2016-04-04 22:44:32 +02:00
}
2018-05-28 16:50:16 +02:00
}
}
};
2016-04-04 22:44:32 +02:00
// Loop through each passed argument
for (let i = 0; i < length; i++) {
// store argument at position i
2017-08-16 10:47:22 +02:00
const obj = arguments[i];
2016-04-04 22:44:32 +02:00
2016-09-15 22:04:15 +02:00
// If we are in fact dealing with an object, merge it.
if (isType('Object', obj)) {
merge(obj);
2016-04-04 22:44:32 +02:00
}
}
2016-04-04 22:44:32 +02:00
return extended;
2016-04-04 22:44:32 +02:00
};
2016-03-16 21:24:11 +01:00
export const wrap = function(element, wrapper) {
wrapper = wrapper || document.createElement('div');
if (element.nextSibling) {
element.parentNode.insertBefore(wrapper, element.nextSibling);
} else {
element.parentNode.appendChild(wrapper);
}
return wrapper.appendChild(element);
2016-03-16 21:24:11 +01:00
};
2016-08-14 23:14:37 +02:00
/**
2016-03-16 21:24:11 +01:00
* Find ancestor in DOM tree
2016-08-14 23:14:37 +02:00
* @param {NodeElement} el Element to start search from
2016-03-16 21:24:11 +01:00
* @param {[type]} cls Class of parent
* @return {NodeElement} Found parent element
*/
export const findAncestor = function(el, cls) {
while ((el = el.parentElement) && !el.classList.contains(cls));
return el;
2016-03-16 21:24:11 +01:00
};
2017-03-12 14:17:46 +01:00
/**
* Find ancestor in DOM tree by attribute name
* @param {NodeElement} el Element to start search from
* @param {string} attr Attribute name of parent
* @return {?NodeElement} Found parent element or null
*/
2017-03-12 14:21:00 +01:00
export const findAncestorByAttrName = function(el, attr) {
2017-03-12 14:17:46 +01:00
let target = el;
while (target) {
if (target.hasAttribute(attr)) {
return target;
}
target = target.parentElement;
}
return null;
};
/**
2016-08-14 23:14:37 +02:00
* Get the next or previous element from a given start point
* @param {HTMLElement} startEl Element to start position from
2016-08-14 23:14:37 +02:00
* @param {String} className The class we will look through
* @param {Number} direction Positive next element, negative previous element
* @return {[HTMLElement} Found element
*/
export const getAdjacentEl = (startEl, className, direction = 1) => {
if (!startEl || !className) return;
const parent = startEl.parentNode.parentNode;
const children = Array.from(parent.querySelectorAll(className));
const startPos = children.indexOf(startEl);
const operatorDirection = direction > 0 ? 1 : -1;
2016-08-14 23:14:37 +02:00
return children[startPos + operatorDirection];
};
/**
2016-08-14 23:14:37 +02:00
* Determine whether an element is within
* @param {HTMLElement} el Element to test
* @param {HTMLElement} parent Scrolling parent
2016-08-14 23:14:37 +02:00
* @param {Number} direction Whether element is visible from above or below
* @return {Boolean}
*/
export const isScrolledIntoView = (el, parent, direction = 1) => {
if (!el) return;
let isVisible;
if (direction > 0) {
// In view from bottom
2018-05-28 16:50:16 +02:00
isVisible =
parent.scrollTop + parent.offsetHeight >= el.offsetTop + el.offsetHeight;
} else {
// In view from top
isVisible = el.offsetTop >= parent.scrollTop;
}
2016-08-14 23:14:37 +02:00
return isVisible;
2016-08-14 23:14:37 +02:00
};
2016-05-02 16:29:05 +02:00
2016-03-16 21:24:11 +01:00
/**
* Escapes html in the string
* @param {String} html Initial string/html
2016-03-16 21:24:11 +01:00
* @return {String} Sanitised string
*/
export const stripHTML = html =>
2018-05-28 16:50:16 +02:00
html
.replace(/&/g, '&amp;')
.replace(/>/g, '&rt;')
.replace(/</g, '&lt;')
.replace(/"/g, '&quot;');
2016-03-16 21:24:11 +01:00
/**
* Get a random number between a range
* @param {Number} min Minimum range
* @param {Number} max Maximum range
* @return {Number} Random number
*/
export const getRandomNumber = function(min, max) {
return Math.floor(Math.random() * (max - min) + min);
2016-08-14 23:14:37 +02:00
};
/**
* Turn a string into a node
* @param {String} String to convert
* @return {HTMLElement} Converted node element
*/
export const strToEl = (function() {
2017-08-16 10:47:22 +02:00
const tmpEl = document.createElement('div');
return function(str) {
2017-08-16 10:47:22 +02:00
const cleanedInput = str.trim();
let r;
tmpEl.innerHTML = cleanedInput;
r = tmpEl.children[0];
while (tmpEl.firstChild) {
tmpEl.removeChild(tmpEl.firstChild);
}
return r;
};
2018-05-28 16:50:16 +02:00
})();
2016-04-07 20:44:16 +02:00
2016-08-14 23:14:37 +02:00
/**
* Determines the width of a passed input based on its value and passes
* it to the supplied callback function.
2016-04-07 20:44:16 +02:00
*/
export const calcWidthOfInput = (input, callback) => {
const value = input.value || input.placeholder;
let width = input.offsetWidth;
if (value) {
const testEl = strToEl(`<span>${stripHTML(value)}</span>`);
testEl.style.position = 'absolute';
testEl.style.padding = '0';
testEl.style.top = '-9999px';
testEl.style.left = '-9999px';
testEl.style.width = 'auto';
testEl.style.whiteSpace = 'pre';
2017-08-02 15:05:26 +02:00
if (document.body.contains(input) && window.getComputedStyle) {
const inputStyle = window.getComputedStyle(input);
if (inputStyle) {
testEl.style.fontSize = inputStyle.fontSize;
testEl.style.fontFamily = inputStyle.fontFamily;
testEl.style.fontWeight = inputStyle.fontWeight;
testEl.style.fontStyle = inputStyle.fontStyle;
testEl.style.letterSpacing = inputStyle.letterSpacing;
testEl.style.textTransform = inputStyle.textTransform;
testEl.style.padding = inputStyle.padding;
}
}
document.body.appendChild(testEl);
requestAnimationFrame(() => {
if (value && testEl.offsetWidth !== input.offsetWidth) {
width = testEl.offsetWidth + 4;
}
document.body.removeChild(testEl);
callback.call(this, `${width}px`);
});
} else {
callback.call(this, `${width}px`);
}
2016-06-29 15:47:58 +02:00
};
2017-01-01 16:32:09 +01:00
/**
* Sorting function for current and previous string
* @param {String} a Current value
* @param {String} b Next value
* @return {Number} -1 for after previous,
* 1 for before,
* 0 for same location
*/
2016-06-29 15:47:58 +02:00
export const sortByAlpha = (a, b) => {
const labelA = (a.label || a.value).toLowerCase();
const labelB = (b.label || b.value).toLowerCase();
2016-06-29 15:47:58 +02:00
if (labelA < labelB) return -1;
if (labelA > labelB) return 1;
return 0;
};
2017-01-01 16:32:09 +01:00
/**
* Sort by numeric score
* @param {Object} a Current value
* @param {Object} b Next value
* @return {Number} -1 for after previous,
* 1 for before,
* 0 for same location
*/
2017-08-16 10:47:22 +02:00
export const sortByScore = (a, b) => a.score - b.score;
2017-01-01 16:32:09 +01:00
/**
* Dispatch native event
2017-01-01 16:32:09 +01:00
* @param {NodeElement} element Element to trigger event on
* @param {String} type Type of event to trigger
* @param {Object} customArgs Data to pass with event
* @return {Object} Triggered event
*/
export const dispatchEvent = (element, type, customArgs = null) => {
2017-12-20 13:38:16 +01:00
const event = new CustomEvent(type, {
2017-01-01 16:32:09 +01:00
detail: customArgs,
bubbles: true,
2017-08-16 10:47:22 +02:00
cancelable: true,
2017-01-01 16:32:09 +01:00
});
return element.dispatchEvent(event);
};
2017-08-10 12:57:17 +02:00
/**
* Tests value against a regular expression
* @param {string} value Value to test
* @return {Boolean} Whether test passed/failed
* @private
*/
export const regexFilter = (value, regex) => {
if (!value || !regex) {
return false;
}
const expression = new RegExp(regex.source, 'i');
return expression.test(value);
2017-08-16 10:47:22 +02:00
};
2017-12-10 19:00:57 +01:00
export const getWindowHeight = () => {
const body = document.body;
const html = document.documentElement;
return Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight,
);
};
export const reduceToValues = (items, key = 'value') => {
const values = items.reduce((prev, current) => {
prev.push(current[key]);
return prev;
}, []);
return values;
2018-05-28 16:50:16 +02:00
};
/**
* Fetch properties from object
* @param {Object} object Related object
* @param {String} properties Properties from object
*/
2018-05-28 16:50:16 +02:00
export const fetchFromObject = (object, properties) => {
const index = properties.indexOf('.');
2018-05-28 16:50:16 +02:00
if (index > -1) {
return fetchFromObject(
object[properties.substring(0, index)],
properties.substr(index + 1),
);
}
return object[properties];
};
export const isIE11 = () => {
2018-05-28 16:50:16 +02:00
return !!(
navigator.userAgent.match(/Trident/) &&
navigator.userAgent.match(/rv[ :]11/)
);
2018-05-27 18:22:58 +02:00
};
export const existsInArray = (array, value) => {
2018-05-28 16:50:16 +02:00
return array.some(item => {
2018-05-27 18:22:58 +02:00
if (isType('String', value)) {
return item.value === value.trim();
}
return item.value === value;
2018-05-28 16:50:16 +02:00
});
};