add missing functionality to Ox.api (positions requests and enums)

This commit is contained in:
rlx 2012-03-29 18:40:03 +00:00
parent 7b4002b340
commit f3ff4b791d
2 changed files with 164 additions and 42 deletions

View file

@ -2,16 +2,20 @@
/*@ /*@
Ox.api <f> Turns an array into a list API Ox.api <f> Turns an array into a list API
(items) -> <f> List API (items, options) -> <f> List API
(items, keys) -> <f> List API items <[o]> An array of objects (key/value stores)
items <[o]> An array of objects options <o> Options object
keys <[s]> Array of keys to be included in totals enums <o> Enumerables, for example <code>{size: ['S', 'M', 'L', 'XL']}</code>
sums <[s]> List of keys to be included in totals
unique <s|'id'> The name of the unique key
<script> <script>
Ox.test.api = Ox.api([ Ox.test.api = Ox.api([
{id: 'foo', n: 2}, {id: 'foo', n: 2},
{id: 'bar', n: 2}, {id: 'bar', n: 2},
{id: 'baz', n: 1} {id: 'baz', n: 1}
], ['n']); ], {
sums: ['n']
});
Ox.test.apiResults = { Ox.test.apiResults = {
0: Ox.test.api(), 0: Ox.test.api(),
1: Ox.test.api({ 1: Ox.test.api({
@ -25,8 +29,8 @@ Ox.api <f> Turns an array into a list API
keys: [], keys: [],
query: { query: {
conditions: [ conditions: [
{key: 'id', value: 'f', operator: '!^'}, {key: 'id', operator: '!^', value: 'f'},
{key: 'n', value: 1, operator: '>'} {key: 'n', operator: '>', value: 1}
], ],
operator: '&' operator: '&'
} }
@ -35,8 +39,8 @@ Ox.api <f> Turns an array into a list API
keys: [], keys: [],
query: { query: {
conditions: [ conditions: [
{key: 'id', value: 'O', operator: '='}, {key: 'id', operator: '=', value: 'O'},
{key: 'n', value: [1, 2], operator: '='} {key: 'n', operator: '=', value: [1, 2]}
], ],
operator: '|' operator: '|'
}, },
@ -46,11 +50,11 @@ Ox.api <f> Turns an array into a list API
keys: [], keys: [],
query: { query: {
conditions: [ conditions: [
{key: 'id', value: 'f', operator: '='}, {key: 'id', operator: '=', value: 'f'},
{ {
conditions: [ conditions: [
{key: 'id', value: 'a', operator: '='}, {key: 'id', operator: '=', value: 'a'},
{key: 'id', value: 'z', operator: '='} {key: 'id', operator: '=', value: 'z'}
], ],
operator: '&' operator: '&'
} }
@ -63,8 +67,28 @@ Ox.api <f> Turns an array into a list API
keys: [], keys: [],
range: [1, 2], range: [1, 2],
sort: ['+id'] sort: ['+id']
}),
7: Ox.test.api({
positions: ['foo', 'bar'],
sort: ['+id']
}) })
}; };
Ox.test.api = Ox.api([
{i: 0, size: 'S'},
{i: 1, size: 'M'},
{i: 2, size: 'L'}
], {
enums: {size: ['S', 'M', 'L']},
unique: 'i'
});
Ox.test.apiResults[8] = Ox.test.api({
keys: ['size'],
query: {
conditions: [{key: 'size', operator: '>=', value: 'M'}],
operator: '&'
},
sort: [{key: 'size', operator: '-'}]
});
</script> </script>
> Ox.test.apiResults[0].data > Ox.test.apiResults[0].data
{items: 3, n: 5} {items: 3, n: 5}
@ -80,9 +104,67 @@ Ox.api <f> Turns an array into a list API
{items: [{id: 'baz', n: 1}, {id: 'foo', n: 2}]} {items: [{id: 'baz', n: 1}, {id: 'foo', n: 2}]}
> Ox.test.apiResults[6].data > Ox.test.apiResults[6].data
{items: [{id: 'baz', n: 1}]} {items: [{id: 'baz', n: 1}]}
> Ox.test.apiResults[7].data
{positions: {foo: 2, bar: 0}}
> Ox.test.apiResults[8].data
{items: [{i: 2, size: 'L'}, {i: 1, size: 'M'}]}
@*/ @*/
Ox.api = function(items, keys) { Ox.api = function(items, options) {
keys = keys || [];
var enums = options.enums ? parseEnums(options.enums) : {},
sums = options.sums || [],
unique = options.unique || 'id';
function parseEnums(enums) {
// make enumerable strings lowercase
return Ox.map(enums, function(values) {
return values.map(function(value) {
return value.toLowerCase();
});
});
}
function parseConditions(conditions) {
// make string values lowercase,
// and replace enumerable strings used with the
// <, !<, <=, !<=, >, !>, >= or !>= operator
// with their index
return conditions.map(function(condition) {
var key = condition.key,
operator = condition.operator,
values = Ox.toArray(condition.value);
if (condition.conditions) {
condition.conditions = parseConditions(condition.conditions);
} else {
values = values.map(function(value) {
if (Ox.isString(value)) {
value = value.toLowerCase();
}
if (enums[key] && (
operator.indexOf('<') > -1
|| operator.indexOf('>') > -1
)) {
value = enums[key].indexOf(value);
}
return value;
});
condition.value = Ox.isArray(condition.value)
? values : values[0];
}
return condition;
});
}
function parseSort(sort) {
// translate 'foo' to {key: 'foo', operator: '+'}
return sort.map(function(sort) {
return Ox.isString(sort) ? {
key: sort.replace(/^[\+\-]/, ''),
operator: sort[0] == '-' ? '-' : '+'
} : sort;
});
}
function testCondition(item, condition) { function testCondition(item, condition) {
var key = condition.key, var key = condition.key,
operator = condition.operator.replace('!', ''), operator = condition.operator.replace('!', ''),
@ -97,20 +179,24 @@ Ox.api = function(items, keys) {
}, },
'==': function(a, b) { return a === b; }, '==': function(a, b) { return a === b; },
'<': function(a, b) { return a < b; }, '<': function(a, b) { return a < b; },
'>': function(a, b) { return a > b; },
'<=': function(a, b) { return a <= b; }, '<=': function(a, b) { return a <= b; },
'>': function(a, b) { return a > b; },
'>=': function(a, b) { return a >= b; }, '>=': function(a, b) { return a >= b; },
'^': function(a, b) { return Ox.starts(a, b); }, '^': function(a, b) { return Ox.starts(a, b); },
'$': function(a, b) { return Ox.ends(a, b); }, '$': function(a, b) { return Ox.ends(a, b); },
}; };
if (Ox.isString(value)) {
value = value.toLowerCase();
}
if (Ox.isString(itemValue)) { if (Ox.isString(itemValue)) {
itemValue = itemValue.toLowerCase(); itemValue = itemValue.toLowerCase();
} }
if (enums[key] && (
operator.indexOf('<') > -1
|| operator.indexOf('>') > -1
)) {
itemValue = enums[key].indexOf(itemValue);
}
return test[operator](itemValue, value) == !not; return test[operator](itemValue, value) == !not;
} }
function testQuery(item, query) { function testQuery(item, query) {
var match = true; var match = true;
Ox.forEach(query.conditions, function(condition) { Ox.forEach(query.conditions, function(condition) {
@ -128,22 +214,55 @@ Ox.api = function(items, keys) {
}); });
return match; return match;
} }
return function(options) { return function(options) {
var data, var data,
result = {data: {}, status: {code: 200, text: 'ok'}}; result = {data: {}, status: {code: 200, text: 'ok'}},
sort = {};
options = options || {}; options = options || {};
if (options.query) { if (options.query) {
// find
options.query.conditions = parseConditions(options.query.conditions);
result.data.items = items.filter(function(item) { result.data.items = items.filter(function(item) {
return testQuery(item, options.query); return testQuery(item, options.query);
}); });
} else { } else {
result.data.items = Ox.clone(items); result.data.items = Ox.clone(items);
} }
if (options.keys) {
if (options.sort) { if (options.sort) {
result.data.items = Ox.sortBy(result.data.items, options.sort); // sort
options.sort = parseSort(options.sort);
Ox.forEach(enums, function(values, key) {
sort[key] = function(value) {
return values.indexOf(value.toLowerCase());
};
})
result.data.items = Ox.sortBy(result.data.items, options.sort, sort);
} }
if (options.positions) {
// return positions
data = {positions: {}};
options.positions.forEach(function(id) {
data.positions[id] = Ox.getIndex(result.data.items, unique, id)
});
result.data = data;
} else if (!options.keys) {
// return totals
data = {};
sums.forEach(function(key) {
data[key] = Ox.sum(result.data.items.map(function(item) {
return item[key];
}));
})
data.items = result.data.items.length;
result.data = data;
} else {
// return items
if (!Ox.isEmpty(options.keys)) { if (!Ox.isEmpty(options.keys)) {
// filter keys
if (options.keys.indexOf(unique) == -1) {
options.keys.push(unique);
}
result.data.items = result.data.items.map(function(item) { result.data.items = result.data.items.map(function(item) {
var ret = {}; var ret = {};
options.keys.forEach(function(key) { options.keys.forEach(function(key) {
@ -153,21 +272,15 @@ Ox.api = function(items, keys) {
}); });
} }
if (options.range) { if (options.range) {
// apply range
result.data.items = Ox.sub( result.data.items = Ox.sub(
result.data.items, options.range[0], options.range[1] result.data.items, options.range[0], options.range[1]
); );
} }
} else {
data = {items: result.data.items.length};
keys.forEach(function(key) {
data[key] = Ox.sum(result.data.items.map(function(item) {
return item[key];
}));
})
result.data = data;
} }
return result; return result;
}; };
}; };
/*@ /*@
@ -259,8 +372,11 @@ Ox.range = function() {
len, matches = {}, sort = {}; len, matches = {}, sort = {};
// find leading numbers // find leading numbers
arr.forEach(function(val, i) { arr.forEach(function(val, i) {
var match = /^\d+/.exec(arr_[i]); var match;
if (Ox.isString(val)) {
match = /^\d+/.exec(arr_[i]);
matches[val] = match ? match[0] : ''; matches[val] = match ? match[0] : '';
}
}); });
// get length of longest leading number // get length of longest leading number
len = Ox.max(Ox.map(matches, function(val) { len = Ox.max(Ox.map(matches, function(val) {
@ -269,13 +385,15 @@ Ox.range = function() {
// pad leading numbers, make lowercase, // pad leading numbers, make lowercase,
// and remove leading non-word characters // and remove leading non-word characters
arr.forEach(function(val, i) { arr.forEach(function(val, i) {
sort[val] = ( sort[val] = Ox.isString(arr_[i])
? (
matches[val] matches[val]
? arr_[i].toString().replace( ? arr_[i].replace(
matches[val], Ox.pad(matches[val], len) matches[val], Ox.pad(matches[val], len)
) )
: arr_[i] : arr_[i]
).toLowerCase().replace(/^\W+/, ''); ).toLowerCase().replace(/^\W+/, '')
: arr_[i];
}); });
return sort; return sort;
} }
@ -320,10 +438,10 @@ Ox.range = function() {
Ox.sortBy = function(arr, by, fn) { Ox.sortBy = function(arr, by, fn) {
var length = by.length, values = {}; var length = by.length, values = {};
by = by.map(function(v) { by = by.map(function(v) {
return { return Ox.isString(v) ? {
key: v.replace(/^[\+\-]/, ''), key: v.replace(/^[\+\-]/, ''),
operator: v[0] == '-' ? '-' : '+' operator: v[0] == '-' ? '-' : '+'
}; } : v;
}); });
fn = fn || {}; fn = fn || {};
by.map(function(v) { by.map(function(v) {

View file

@ -46,6 +46,8 @@ Ox.endsWith <f> Checks if a string ends with a given substring
@*/ @*/
Ox.ends = Ox.endsWith = function(str, sub) { Ox.ends = Ox.endsWith = function(str, sub) {
// fixme: rename to ends // fixme: rename to ends
str = str.toString();
sub = sub.toString();
return str.substr(str.length - sub.length) == sub; return str.substr(str.length - sub.length) == sub;
}; };
@ -427,6 +429,8 @@ Ox.startsWith <f> Checks if a string starts with a given substring
@*/ @*/
Ox.starts = Ox.startsWith = function(str, sub) { Ox.starts = Ox.startsWith = function(str, sub) {
// fixme: rename to starts // fixme: rename to starts
str = str.toString();
sub = sub.toString();
return str.substr(0, sub.length) == sub; return str.substr(0, sub.length) == sub;
}; };