UNPKG

angular2-json-schema-form

Version:
270 lines (260 loc) 9.79 kB
import { hasValue, inArray, isArray, isDefined, isObject, isEmpty, isMap, isSet, isString, PlainObject } from './validator.functions'; /** * Utility function library: * * addClasses, copy, forEach, forEachCopy, hasOwn, * mergeFilteredObject, parseText, toTitleCase */ /** * 'addClasses' function * * @param {string | string[] | Set<string>} oldClasses * @param {string | string[] | Set<string>} newClasses * @return {string | string[] | Set<string>} - Combined classes */ export function addClasses( oldClasses: string | string[] | Set<string>, newClasses: string | string[] | Set<string> ): string | string[] | Set<string> { const badType = i => !isSet(i) && !isArray(i) && !isString(i); if (badType(newClasses)) { return oldClasses; } if (badType(oldClasses)) { oldClasses = ''; } const toSet = i => isSet(i) ? i : isArray(i) ? new Set(i) : new Set(i.split(' ')); let combinedSet: Set<any> = toSet(oldClasses); let newSet: Set<any> = toSet(newClasses); newSet.forEach(c => combinedSet.add(c)); if (isSet(oldClasses)) { return combinedSet; } if (isArray(oldClasses)) { return Array.from(combinedSet); } return Array.from(combinedSet).join(' '); } /** * 'copy' function * * Makes a shallow copy of a JavaScript object, array, Map, or Set. * If passed a JavaScript primitive value (string, number, boolean, or null), * it returns the value. * * @param {Object|Array|string|number|boolean|null} object - The object to copy * @return {Object|Array|string|number|boolean|null} - The copied object */ export function copy(object: any): any { if (typeof object !== 'object' || object === null) { return object; } if (isObject(object)) { return Object.assign({}, object); } if (isArray(object)) { return [].concat(object); } if (isMap(object)) { return new Map(object); } if (isSet(object)) { return new Set(object); } console.error('copy error: Object to copy must be a JavaScript object or value.'); } /** * 'forEach' function * * Iterates over all items in the first level of an object or array * and calls an iterator funciton on each item. * * The iterator function is called with four values: * 1. The current item's value * 2. The current item's key * 3. The parent object, which contains the current item * 4. The root object * * Setting the optional third parameter to 'top-down' or 'bottom-up' will cause * it to also recursively iterate over items in sub-objects or sub-arrays in the * specified direction. * * @param {Object|Array} object - The object or array to iterate over * @param {function} fn - the iterator funciton to call on each item * @return {void} */ export function forEach( object: any, fn: (v: any, k?: string | number, c?: any, rc?: any) => any, recurse: boolean | string = false, rootObject: any = object ): void { if (isEmpty(object)) { return; } if ((isObject(object) || isArray(object)) && typeof fn === 'function') { for (let key of Object.keys(object)) { const value = object[key]; if (recurse === 'bottom-up' && (isObject(value) || isArray(value))) { forEach(value, fn, recurse, rootObject); } fn(value, key, object, rootObject); if (recurse === 'top-down' && (isObject(value) || isArray(value))) { forEach(value, fn, recurse, rootObject); } } } else if (typeof fn !== 'function') { console.error('forEach error: Iterator must be a function.'); console.error(fn); } else { console.error('forEach error: Input object must be an object or array.'); console.error(object); } } /** * 'forEachCopy' function * * Iterates over all items in the first level of an object or array * and calls an iterator function on each item. Returns a new object or array * with the same keys or indexes as the original, and values set to the results * of the iterator function. * * Does NOT recursively iterate over items in sub-objects or sub-arrays. * * @param {Object|Array} object - The object or array to iterate over * @param {function} fn - The iterator funciton to call on each item * @param {any = null} context - Context in which to call the iterator function * @return {Object|Array} - The resulting object or array */ export function forEachCopy( object: any, fn: (v: any, k?: string | number, o?: any, p?: string) => any ): any { if (!hasValue(object)) { return; } if ((isObject(object) || isArray(object)) && typeof fn !== 'function') { let newObject: any = isArray(object) ? [] : {}; for (let key of Object.keys(object)) { newObject[key] = fn(object[key], key, object); } return newObject; } if (typeof fn !== 'function') { console.error('forEachCopy error: Iterator must be a function.'); console.error(fn); } else { console.error('forEachCopy error: Input object must be an object or array.'); console.error(object); } } /** * 'hasOwn' utility function * * Checks whether an object has a particular property. * * @param {any} object - the object to check * @param {string} property - the property to look for * @return {boolean} - true if object has property, false if not */ export function hasOwn(object: any, property: string): boolean { if (!isObject(object) && !isArray(object)) { return false; } return object.hasOwnProperty(property); } /** * 'mergeFilteredObject' utility function * * Shallowly merges two objects, setting key and values from source object * in target object, excluding specified keys. * * Optionally, it can also use functions to transform the key names and/or * the values of the merging object. * * @param {PlainObject} targetObject - Target object to add keys and values to * @param {PlainObject} sourceObject - Source object to copy keys and values from * @param {string[]} excludeKeys - Array of keys to exclude * @param {(string) => string = (k) => k} keyFn - Function to apply to keys * @param {(any) => any = (v) => v} valueFn - Function to apply to values * @return {PlainObject} - Returns targetObject */ export function mergeFilteredObject( targetObject: PlainObject, sourceObject: PlainObject, excludeKeys: any[] = [], keyFn: (string) => string = (k) => k, valueFn: (any) => any = (v) => v ): PlainObject { if (!isObject(sourceObject)) { return targetObject; } if (!isObject(targetObject)) { targetObject = {}; } for (let key of Object.keys(sourceObject)) { if (!inArray(key, excludeKeys) && isDefined(sourceObject[key])) { targetObject[keyFn(key)] = valueFn(sourceObject[key]); } } return targetObject; } /** * 'parseText' function * * @param {string = ''} text - * @param {any = {}} value - * @param {number = null} index - * @return {string} - */ export function parseText( text: string = '', value: any = {}, values: any = {}, key: number|string = null, tpldata: any = null ): string { if (!text) { return text; } let idx: number = null; // For JSON Form API compatibility let $index: number = null; // For Angular Schema Form API compatibility if (typeof key === 'number') { idx = $index = key + 1; } try { return text.replace(/{{.+?}}/g, exp => eval(exp.slice(2, -2))); } catch (error) { try { return (tpldata) ? text.replace(/{{.+?}}/g, exp => eval('tpldata.' + exp.slice(2, -2))) : text.replace(/{{.+?}}/g, exp => eval('this.' + exp.slice(2, -2))); } catch (error) { } console.error('parseText error: '); console.error(error); return text; } } /** * 'toTitleCase' function * * Intelligently converts an input string to Title Case. * * Accepts an optional second parameter with a list of additional * words and abbreviations to force into a particular case. * * This function is built on prior work by John Gruber and David Gouch: * http://daringfireball.net/2008/08/title_case_update * https://github.com/gouch/to-title-case * * @param {string} input - * @param {string|string[]} forceWords? - * @return {string} - */ export function toTitleCase(input: string, forceWords?: string|string[]): string { if (!isString(input)) { return input; } let forceArray: string[] = ['a', 'an', 'and', 'as', 'at', 'but', 'by', 'en', 'for', 'if', 'in', 'nor', 'of', 'on', 'or', 'per', 'the', 'to', 'v', 'v.', 'vs', 'vs.', 'via']; if (isString(forceWords)) { forceWords = forceWords.split('|'); } if (isArray(forceWords)) { forceArray = forceArray.concat(forceWords); } const forceArrayLower: string[] = forceArray.map(w => w.toLowerCase()); const noInitialCase: boolean = input === input.toUpperCase() || input === input.toLowerCase(); let prevLastChar: string = ''; input = input.trim(); return input.replace(/[A-Za-z0-9\u00C0-\u00FF]+[^\s-]*/g, (word, idx) => { if (!noInitialCase && word.slice(1).search(/[A-Z]|\../) !== -1) { return word; } else { let newWord: string; const forceWord: string = forceArray[forceArrayLower.indexOf(word.toLowerCase())]; if (!forceWord) { if (noInitialCase) { if (word.slice(1).search(/\../) !== -1) { newWord = word.toLowerCase(); } else { newWord = word[0].toUpperCase() + word.slice(1).toLowerCase(); } } else { newWord = word[0].toUpperCase() + word.slice(1); } } else if ( forceWord === forceWord.toLowerCase() && ( idx === 0 || idx + word.length === input.length || prevLastChar === ':' || input[idx - 1].search(/[^\s-]/) !== -1 || (input[idx - 1] !== '-' && input[idx + word.length] === '-') ) ) { newWord = forceWord[0].toUpperCase() + forceWord.slice(1); } else { newWord = forceWord; } prevLastChar = word.slice(-1); return newWord; } }); };