/** * Array utilities - ES Module Version */ import { isArray, isBoolean, isEqual, isFunction, isNumber, isObject, isString, isUndefined, typeOf } from './Type.js'; import { extend, filter, forEach, len, map, some } from './Collection.js'; import { clone, getset, isEmpty, makeObject } from './Object.js'; import { cache, identity } from './Function.js'; import { MAX_LATITUDE } from './Constants.js'; import { deg, mod } from './Math.js'; /** * Turns an array into a list API */ export function api(items, options) { options = options || {}; var api = { cache: options.cache, enums: options.enums ? parseEnums(options.enums) : {}, geo: options.geo, map: options.map || {}, sort: options.sort || [], sums: options.sums || [], unique: options.unique || 'id' }, fn = function(options, callback) { var data, keys, map = {}, result = {data: {}, status: {code: 200, text: 'ok'}}; options = options || {}; if (options.query) { // find options.query.conditions = parseConditions(options.query.conditions); result.data.items = items.filter(function(item) { return testQuery(item, options.query); }); } else { result.data.items = clone(items); } if (options.sort && result.data.items.length > 1) { // sort keys = []; options.sort = parseSort( options.sort.concat(api.sort) ).filter(function(v) { var ret = keys.indexOf(v.key) == -1; if (ret) { keys.push(v.key); } return ret; }); options.sort.forEach(function(v) { var key = v.key; if (api.enums[key]) { map[key] = function(value) { return api.enums[key].indexOf(value); }; } else if (api.map[key]) { map[key] = api.map[key]; } }); result.data.items = sortBy( result.data.items, options.sort, map ); } if (options.positions) { // return positions data = {}; options.positions.forEach(function(id) { data[id] = getIndex( result.data.items, api.unique, id ); }); result.data = {positions: data}; } else if (!options.keys) { // return totals data = {}; api.sums.forEach(function(key) { data[key] = sum(result.data.items, key); }); if (api.geo) { /* not implemented */ } result.data = extend(data, { items: result.data.items.length }); } else { // return items if (!isUndefined(options.range)) { // range result.data.items = result.data.items.slice( options.range[0], options.range[1] ); } if (options.keys) { // keys result.data.items = result.data.items.map(function(item) { var ret = {}; options.keys.forEach(function(key) { ret[key] = item[key]; }); return ret; }); } result.data = {items: result.data.items}; } if (callback) { callback(result); } return result; }; function parseConditions(conditions) { return conditions.map(function(condition) { var ret = extend({}, condition); if (ret.conditions) { ret.conditions = parseConditions(ret.conditions); } else { ret.value = isArray(ret.value) ? ret.value.map(parseValue) : parseValue(ret.value); } return ret; }); } function parseEnums(enums) { var ret = {}; forEach(enums, function(values, key) { ret[key] = values; }); return ret; } function parseSort(sort) { return sort.map(function(value) { return isString(value) ? { key: value.slice(1), operator: value[0] == '+' ? '+' : '-' } : value; }); } function parseValue(value) { return isString(value) ? value.toLowerCase() : value; } function testCondition(item, condition) { var key = condition.key, operator = condition.operator.replace(/^!/, ''), value = condition.value, itemValue = map[key] ? map[key](item[key], item) : item[key], test = { '=': function(a, b) { return isArray(b) ? b.indexOf(a) > -1 : isString(a) ? a.toLowerCase().indexOf(b) > -1 : a === b; }, '==': function(a, b) { return isArray(b) ? b.indexOf(a) > -1 : a === b; }, '<': function(a, b) { return isString(a) ? a.toLowerCase().localeCompare(b) == -1 : a < b; }, '<=': function(a, b) { return isString(a) ? a.toLowerCase().localeCompare(b) < 1 : a <= b; }, '>': function(a, b) { return isString(a) ? a.toLowerCase().localeCompare(b) == 1 : a > b; }, '>=': function(a, b) { return isString(a) ? a.toLowerCase().localeCompare(b) > -1 : a >= b; }, '^': function(a, b) { return isArray(b) ? b.some(function(v) { return startsWith(a, v); }) : startsWith(a, b); }, '$': function(a, b) { return isArray(b) ? b.some(function(v) { return endsWith(a, v); }) : endsWith(a, b); } }, ret = test[operator](itemValue, value); return condition.operator[0] == '!' ? !ret : ret; } function testQuery(item, query) { var ret = query.operator == '&'; forEach(query.conditions, function(condition) { var test = condition.conditions ? testQuery(item, condition) : testCondition(item, condition); if (test && query.operator == '|') { ret = true; return false; // break } else if (!test && query.operator == '&') { ret = false; return false; // break } }); return ret; } fn.enums = api.enums; fn.map = api.map; fn.sort = api.sort; fn.sums = api.sums; if (api.cache) { fn = cache(fn, {async: true}); } return fn; } /** * Removes `null` or `undefined` values from an array */ export function compact(array) { return filter(array, function(value) { return value != null; }); } /** * Counts the number of elements in an array for which the condition is true */ export function count(array, fn) { return filter(array, fn || identity).length; } /** * Sorts strings naturally */ export function sort(array, map) { var values = getSortValues(map ? array.map(map) : array); return array.sort(function(a, b) { a = map ? map(a) : a; b = map ? map(b) : b; return values[a] < values[b] ? -1 : values[a] > values[b] ? 1 : 0; }); } /** * Returns the unique values of an array */ export function unique(array, fn) { var ret = []; if (array.length) { if (!fn) { ret = array.filter(function(value, index) { return array.indexOf(value) == index; }); } else { array.forEach(function(value) { var key = fn(value); if (!ret.some(function(value) { return fn(value) === key; })) { ret.push(value); } }); } } return ret; } /** * Zips an array of arrays */ export function zip() { var args = Array.prototype.slice.call(arguments); return args[0].map(function(value, index) { return args.map(function(array) { return array[index]; }); }); } // Helper functions (these would be imported from other modules in a complete implementation) function startsWith(str, substr) { return isString(str) && str.toLowerCase().indexOf(substr) === 0; } function endsWith(str, substr) { return isString(str) && str.toLowerCase().indexOf(substr) === str.length - substr.length; } function getIndex(array, key, value) { var index = -1; array.some(function(item, i) { if (item[key] === value) { index = i; return true; } }); return index; } function sum(array, key) { return array.reduce(function(sum, item) { return sum + (item[key] || 0); }, 0); } function sortBy(array, sort, map) { var values = {}; sort = sort || []; // Get sort values for each key sort.forEach(function(s) { var key = s.key; var fn = map && map[key] ? map[key] : identity; values[key] = {}; array.forEach(function(item) { var value = fn(item[key], item); if (!values[key][value]) { values[key][value] = getSortValue( isString(value) ? value.toLowerCase() : value ); } }); }); return array.sort(function(a, b) { var ret = 0; sort.some(function(s) { var key = s.key, fn = map && map[key] ? map[key] : identity, aValue = fn(a[key], a), bValue = fn(b[key], b), aSortValue = values[key][aValue], bSortValue = values[key][bValue]; if (aSortValue < bSortValue) { ret = s.operator == '+' ? -1 : 1; } else if (aSortValue > bSortValue) { ret = s.operator == '+' ? 1 : -1; } return ret !== 0; }); return ret; }); } function getSortValue(value) { var sortValue = value; if (isString(value)) { sortValue = value.toLowerCase() .replace(/^\W+/, '') .replace(/\d+/g, function(match) { return match.padStart(16, '0'); }); } return sortValue; } function getSortValues(array) { var values = {}; array.forEach(function(value) { if (!values[value]) { values[value] = getSortValue(value); } }); return values; } // Export all functions export default { api, compact, count, sort, unique, zip };