In Ox.api, add a map option {key: fn(value, object)}, value being object[key], so that the sort value can depend on other keys; Ox.sortBy is updated to use getSortValue (cached sort value of mapped value) instead of getSortValues (precomputed mapped sort value per value, which is no longer unique)

This commit is contained in:
rolux 2012-09-03 22:11:25 +02:00
parent baf29d068b
commit f5c320187e

View file

@ -9,6 +9,7 @@ Ox.api <f> Turns an array into a list API
cache <b|false> If true, cache results cache <b|false> If true, cache results
enums <o> Enumerables, for example `{size: ['S', 'M', 'L']}` enums <o> Enumerables, for example `{size: ['S', 'M', 'L']}`
geo <b|false> If true, return combined area with totals geo <b|false> If true, return combined area with totals
map <o> Sort map, for example `{name: function(v, o) { return o.sortName; }}`
sort <[o]|[s]> Default sort, for example `['+name', '-age']` sort <[o]|[s]> Default sort, for example `['+name', '-age']`
sums <[s]> List of keys to be included in totals sums <[s]> List of keys to be included in totals
unique <s|'id'> The name of the unique key unique <s|'id'> The name of the unique key
@ -129,6 +130,17 @@ Ox.api <f> Turns an array into a list API
}, },
sort: [{key: 'size', operator: '-'}] sort: [{key: 'size', operator: '-'}]
}); });
Ox.test.api = Ox.api([
{name: 'John Cale', sortName: 'Cale, John'},
{name: 'Brian Eno', sortName: 'Eno, Brian'}
], {
map: {name: function(value, object) { return object.sortName; }},
unique: 'name'
});
Ox.test.apiResults[9] = Ox.test.api({
keys: ['name'],
sort: [{key: 'name', operator: '+'}]
});
</script> </script>
> Ox.test.apiResults[0].data > Ox.test.apiResults[0].data
{items: 3, n: 5} {items: 3, n: 5}
@ -148,6 +160,8 @@ Ox.api <f> Turns an array into a list API
{positions: {foo: 2, bar: 0}} {positions: {foo: 2, bar: 0}}
> Ox.test.apiResults[8].data > Ox.test.apiResults[8].data
{items: [{i: 2, size: 'L'}, {i: 1, size: 'M'}]} {items: [{i: 2, size: 'L'}, {i: 1, size: 'M'}]}
> Ox.test.apiResults[9].data
{items: [{name: 'John Cale'}, {name: 'Brian Eno'}]}
@*/ @*/
Ox.api = function(items, options) { Ox.api = function(items, options) {
@ -155,6 +169,7 @@ Ox.api = function(items, options) {
cache: options.cache, cache: options.cache,
enums: options.enums ? parseEnums(options.enums) : {}, enums: options.enums ? parseEnums(options.enums) : {},
geo: options.geo, geo: options.geo,
map: options.map || {},
sort: options.sort || [], sort: options.sort || [],
sums: options.sums || [], sums: options.sums || [],
unique: options.unique || 'id' unique: options.unique || 'id'
@ -162,8 +177,8 @@ Ox.api = function(items, options) {
fn = function(options, callback) { fn = function(options, callback) {
var data, var data,
keys, keys,
result = {data: {}, status: {code: 200, text: 'ok'}}, map = {},
sort = {}; result = {data: {}, status: {code: 200, text: 'ok'}};
options = options || {}; options = options || {};
if (options.query) { if (options.query) {
// find // find
@ -187,9 +202,11 @@ Ox.api = function(items, options) {
options.sort.forEach(function(v) { options.sort.forEach(function(v) {
var key = v.key; var key = v.key;
if (api.enums[key]) { if (api.enums[key]) {
sort[key] = function(value) { map[key] = function(value) {
return api.enums[key].indexOf(value.toLowerCase()); return api.enums[key].indexOf(value.toLowerCase());
}; };
} else if (api.map[key]) {
map[key] = api.map[key];
}/* else if (Ox.isArray(items[0][key])) { }/* else if (Ox.isArray(items[0][key])) {
sort[key] = function(value) { sort[key] = function(value) {
return value.join(', '); return value.join(', ');
@ -197,7 +214,7 @@ Ox.api = function(items, options) {
}*/ }*/
}); });
if (options.keys || options.positions) { if (options.keys || options.positions) {
result.data.items = sortBy(result.data.items, options.sort, sort, options.query); result.data.items = sortBy(result.data.items, options.sort, map, options.query);
} }
} }
if (options.positions) { if (options.positions) {
@ -566,56 +583,36 @@ Ox.range = function() {
(function() { (function() {
function getSortValues(array, map) { var getSortValue = Ox.cache(function getSortValue(value) {
var mappedArray = map ? array.map(map) : array, length = 0, sort = {}; var sortValue = value;
// find numbers, and length of longest number function trim(value) {
array.forEach(function(value, i) { return value.replace(/^\W+/, '');
var mappedValue = mappedArray[i], matches;
if (Ox.isString(mappedValue)) {
matches = mappedValue.match(/\d+/g);
if (matches) {
length = Ox.max(matches.map(function(match) {
return match.length;
}).concat(length));
} }
}
});
// make lowercase, remove leading non-word characters,
// pad numbers and move leading articles to the end
array.forEach(function(value, i) {
function pad(value) {
return value
.replace(/^\W+/, '')
.replace(/\d+/g, function(match) {
return Ox.pad(match, 'left', length, '0');
});
}
var mappedValue = mappedArray[i];
if ( if (
Ox.isEmpty(mappedValue) Ox.isEmpty(value)
|| Ox.isNull(mappedValue) || Ox.isNull(value)
|| Ox.isUndefined(mappedValue) || Ox.isUndefined(value)
) { ) {
sort[value] = null; sortValue = null;
} else if (Ox.isString(mappedValue)) { } else if (Ox.isString(value)) {
sort[value] = pad(mappedValue.toLowerCase()); // make lowercase and remove leading non-word characters
sortValue = trim(value.toLowerCase());
// move leading articles to the end
// and remove leading non-word characters
Ox.forEach(['a', 'an', 'the'], function(article) { Ox.forEach(['a', 'an', 'the'], function(article) {
var length; if (new RegExp('^' + article + ' ').test(sortValue)) {
if (new RegExp('^' + article + ' ').test(sort[value])) { sortValue = trim(sortValue.slice(article.length + 1))
length = article.length; + ', ' + sortValue.slice(0, article.length);
sort[value] = pad(
sort[value].slice(length + 1) + ', '
+ sort[value].slice(0, length)
);
return false; // break return false; // break
} }
}); });
} else { // pad numbers
sort[value] = mappedValue; sortValue = sortValue.replace(/\d+/g, function(match) {
} return Ox.pad(match, 'left', 64, '0');
}); });
return sort;
} }
return sortValue;
});
/*@ /*@
Ox.sort <f> Sorts an array, handling articles and digits, ignoring capitalization Ox.sort <f> Sorts an array, handling articles and digits, ignoring capitalization
@ -637,15 +634,9 @@ Ox.range = function() {
Ox.sort = function(array, map) { Ox.sort = function(array, map) {
var values = getSortValues(map ? array.map(map) : array); var values = getSortValues(map ? array.map(map) : array);
return array.sort(function(a, b) { return array.sort(function(a, b) {
a = map ? map(a) : a; a = getSortValue(map ? map(a) : a);
b = map ? map(b) : b; b = getSortValue(map ? map(b) : b);
var ret = 0; return a < b ? -1 : a > b ? 1 : 0;
if (values[a] < values[b]) {
ret = -1;
} else if (values[a] > values[b]) {
ret = 1;
}
return ret;
}); });
}; };
@ -661,28 +652,24 @@ Ox.range = function() {
[{id: 1, name: '8 Women'}, {id: 0, name: '80 Days'}] [{id: 1, name: '8 Women'}, {id: 0, name: '80 Days'}]
@*/ @*/
Ox.sortBy = function(array, by, map) { Ox.sortBy = function(array, by, map) {
var values = {}; var sortValues = {};
by = Ox.makeArray(by); by = Ox.makeArray(by).map(function(value) {
map = map || {};
by = by.map(function(value) {
return Ox.isString(value) ? { return Ox.isString(value) ? {
key: value.replace(/^[\+\-]/, ''), key: value.replace(/^[\+\-]/, ''),
operator: value[0] == '-' ? '-' : '+' operator: value[0] == '-' ? '-' : '+'
} : value; } : value;
}); });
by.map(function(value) { map = map || {};
return value.key;
}).forEach(function(key) {
values[key] = getSortValues(array.map(function(value) {
return value[key];
}), map[key]);
});
return array.sort(function(a, b) { return array.sort(function(a, b) {
var aValue, bValue, index = 0, key, ret = 0; var aValue, bValue, index = 0, key, ret = 0;
while (ret == 0 && index < by.length) { while (ret == 0 && index < by.length) {
key = by[index].key; key = by[index].key;
aValue = values[key][a[key]]; aValue = getSortValue(
bValue = values[key][b[key]]; map[key] ? map[key](a[key], a) : a[key]
);
bValue = getSortValue(
map[key] ? map[key](b[key], b) : b[key]
);
if ((aValue === null) != (bValue === null)) { if ((aValue === null) != (bValue === null)) {
ret = aValue === null ? 1 : -1; ret = aValue === null ? 1 : -1;
} else if (aValue < bValue) { } else if (aValue < bValue) {