1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-22 17:33:14 +01:00
onion/js/utils/general_utils.js
2016-01-18 17:31:56 +01:00

301 lines
9.7 KiB
JavaScript

'use strict';
/**
* Checks shallow equality
* Re-export of shallow from shallow-equals
*/
export { default as isShallowEqual } from 'shallow-equals';
/**
* Takes an object and returns a shallow copy without any keys
* that fail the passed in filter function.
* Does not modify the passed in object.
*
* @param {object} obj regular javascript object
* @return {object} regular javascript object without null values or empty strings
*/
export function sanitize(obj, filterFn) {
if (!filterFn) {
// By matching null with a double equal, we can match undefined and null
// http://stackoverflow.com/a/15992131
filterFn = (val) => val == null || val === '';
}
return omitFromObject(obj, filterFn);
}
/**
* Removes all falsy values (undefined, null, false, ...) from a list/array
* @param {array} l the array to sanitize
* @return {array} the sanitized array
*/
export function sanitizeList(l) {
let sanitizedList = [];
for(let i = 0; i < l.length; i++) {
if(l[i]) {
sanitizedList.push(l[i]);
}
}
return sanitizedList;
}
/**
* Sums up a list of numbers. Like a Epsilon-math-kinda-sum...
*/
export function sumNumList(l) {
let sum = 0;
l.forEach((num) => sum += parseFloat(num) || 0);
return sum;
}
/*
Taken from http://stackoverflow.com/a/4795914/1263876
Behaves like C's format string function
*/
export function formatText() {
let args = arguments,
string = args[0],
i = 1;
return string.replace(/%((%)|s|d)/g, (m) => {
// m is the matched format, e.g. %s, %d
let val = null;
if (m[2]) {
val = m[2];
} else {
val = args[i];
// A switch statement so that the formatter can be extended. Default is %s
switch (m) {
case '%d':
val = parseFloat(val);
if (isNaN(val)) {
val = 0;
}
break;
}
i++;
}
return val;
});
}
/**
* Checks a list of objects for key duplicates and returns a boolean
*/
function _doesObjectListHaveDuplicates(l) {
let mergedList = [];
l = l.map((obj) => {
if(!obj) {
throw new Error('The object you are trying to merge is null instead of an empty object');
}
return Object.keys(obj);
});
// Taken from: http://stackoverflow.com/a/10865042
// How to flatten an array of arrays in javascript.
// If two objects contain the same key, then these two keys
// will actually be represented in the merged array
mergedList = mergedList.concat.apply(mergedList, l);
// Taken from: http://stackoverflow.com/a/7376645/1263876
// By casting the array to a set, and then checking if the size of the array
// shrunk in the process of casting, we can check if there were any duplicates
return new Set(mergedList).size !== mergedList.length;
}
/**
* Takes a list of object and merges their keys to one object.
* Uses mergeOptions for two objects.
* @param {[type]} l [description]
* @return {[type]} [description]
*/
export function mergeOptions(...l) {
// If the objects submitted in the list have duplicates,in their key names,
// abort the merge and tell the function's user to check his objects.
if (_doesObjectListHaveDuplicates(l)) {
throw new Error('The objects you submitted for merging have duplicates. Merge aborted.');
}
return Object.assign({}, ...l);
}
/**
* In place update of a dictionary
*/
export function update(a, ...l) {
for(let i = 0; i < l.length; i++) {
for (let attrname in l[i]) {
a[attrname] = l[i][attrname];
}
}
return a;
}
/**
* Escape HTML in a string so it can be injected safely using
* React's `dangerouslySetInnerHTML`
*
* @param s the string to be sanitized
*
* Taken from: http://stackoverflow.com/a/17546215/597097
*/
export function escapeHTML(s) {
return document.createElement('div').appendChild(document.createTextNode(s)).parentNode.innerHTML;
}
/**
* Returns a copy of the given object's own and inherited enumerable
* properties, omitting any keys that pass the given filter function.
*/
function applyFilterOnObject(obj, filterFn) {
const filteredObj = {};
for (let key in obj) {
const val = obj[key];
if (filterFn == null || !filterFn(val, key)) {
filteredObj[key] = val;
}
}
return filteredObj;
}
/**
* Abstraction for selectFromObject and omitFromObject
* for DRYness
* @param {boolean} isInclusion True if the filter should be for including the filtered items
* (ie. selecting only them vs omitting only them)
*/
function filterFromObject(obj, filter, { isInclusion = true } = {}) {
if (filter && filter.constructor === Array) {
return applyFilterOnObject(obj, isInclusion ? ((_, key) => filter.indexOf(key) < 0)
: ((_, key) => filter.indexOf(key) >= 0));
} else if (filter && typeof filter === 'function') {
// Flip the filter fn's return if it's for inclusion
return applyFilterOnObject(obj, isInclusion ? (...args) => !filter(...args)
: filter);
} else {
throw new Error('The given filter is not an array or function. Exclude aborted');
}
}
/**
* Similar to lodash's _.pick(), this returns a copy of the given object's
* own and inherited enumerable properties, selecting only the keys in
* the given array or whose value pass the given filter function.
* @param {object} obj Source object
* @param {array|function} filter Array of key names to select or function to invoke per iteration
* @return {object} The new object
*/
export function selectFromObject(obj, filter) {
return filterFromObject(obj, filter);
}
/**
* Similar to lodash's _.omit(), this returns a copy of the given object's
* own and inherited enumerable properties, omitting any keys that are
* in the given array or whose value pass the given filter function.
* @param {object} obj Source object
* @param {array|function} filter Array of key names to omit or function to invoke per iteration
* @return {object} The new object
*/
export function omitFromObject(obj, filter) {
return filterFromObject(obj, filter, { isInclusion: false });
}
/**
* Recursively tests an object against a "match" object to see if the
* object is similar to the "match" object. In other words, this will
* deeply traverse the "match" object's properties and check them
* against the object by using the testFn.
*
* The object is considered a match if all primitive properties in the
* "match" object are found and accepted in the object by the testFn.
*
* @param {object} obj Object to test
* @param {object} match "Match" object to test against
* @param {(function)} testFn Function to use on each property test.
* Return true to accept the match.
* By default, applies strict equality using ===
* @return {boolean} True if obj matches the "match" object
*/
export function deepMatchObject(obj, match, testFn) {
if (typeof match !== 'object') {
throw new Error('Your specified match argument was not an object');
}
if (typeof testFn !== 'function') {
testFn = (objProp, matchProp) => {
return objProp === matchProp;
};
}
return Object
.keys(match)
.reduce((result, matchKey) => {
if (!result) { return false; }
const objProp = obj[matchKey];
const matchProp = match[matchKey];
if (typeof matchProp === 'object') {
return (typeof objProp === 'object') ? deepMatchObject(objProp, matchProp, testFn)
: false;
} else {
return testFn(objProp, matchProp);
}
}, true);
}
/**
* Takes a string and breaks it at the supplied index and replaces it
* with a (potentially) short string that also has been provided
* @param {string} text The string to truncate
* @param {number} charIndex The char number at which the text should be truncated
* @param {String} replacement All text after charIndex will be replaced with this string
* @return {string} The truncated text
*/
export function truncateTextAtCharIndex(text, charIndex, replacement = '...') {
let truncatedText = '';
truncatedText = text.slice(0, charIndex);
truncatedText += text.length > charIndex ? replacement : '';
return truncatedText;
}
/**
* @param index, int, the starting index of the substring to be replaced
* @param character, substring to be replaced
* @returns {string}
*/
export function replaceSubstringAtIndex(baseString, substrToReplace, stringToBePut) {
let index = baseString.indexOf(substrToReplace);
return baseString.substr(0, index) + stringToBePut + baseString.substr(index + substrToReplace.length);
}
/**
* Extracts the user's subdomain from the browser's window.
* If no subdomain is found (for example on a naked domain), the default "www" is just assumed.
* @return {string} subdomain as a string
*/
export function getSubdomain() {
let { host } = window.location;
let tokens = host.split('.');
return tokens.length > 2 ? tokens[0] : 'www';
}
/**
* Takes two lists and returns their intersection as a list
* @param {Array} a
* @param {Array} b
* @return {[Array]} Intersected list of a and b
*/
export function intersectLists(a, b) {
return a.filter((val) => b.indexOf(val) > -1);
}