oxjs/src/ox/core/Array.js

393 lines
12 KiB
JavaScript
Raw Normal View History

/**
* 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
};