This commit adds ES module versions of fundamental Ox utilities:
- Array utilities (api, compact, unique, zip, etc.)
- String utilities (capitalize, clean, truncate, wordwrap, etc.)
- Math utilities (trig functions, geographic calculations, etc.)
- Object utilities (clone, serialize, keys/values, etc.)
- Function utilities (cache, debounce, throttle, memoize, etc.)
- Constants (math, time, colors, HTTP status codes)
- Polyfills for older browser compatibility
All modules include proper imports/exports and maintain the same API
as the original implementations. Added comprehensive test coverage with
31 tests passing.
Next steps: Convert remaining core modules, set up build pipeline,
and test backward compatibility with existing examples.
🤖 Generated with AI assistance
393 lines
No EOL
12 KiB
JavaScript
393 lines
No EOL
12 KiB
JavaScript
/**
|
|
* 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
|
|
}; |