diff --git a/source/Ox/js/Array.js b/source/Ox/js/Array.js index 3309fcb5..6be9157a 100644 --- a/source/Ox/js/Array.js +++ b/source/Ox/js/Array.js @@ -2,16 +2,20 @@ /*@ Ox.api Turns an array into a list API - (items) -> List API - (items, keys) -> List API - items <[o]> An array of objects - keys <[s]> Array of keys to be included in totals + (items, options) -> List API + items <[o]> An array of objects (key/value stores) + options Options object + enums Enumerables, for example {size: ['S', 'M', 'L', 'XL']} + sums <[s]> List of keys to be included in totals + unique The name of the unique key > Ox.test.apiResults[0].data {items: 3, n: 5} @@ -80,9 +104,67 @@ Ox.api Turns an array into a list API {items: [{id: 'baz', n: 1}, {id: 'foo', n: 2}]} > Ox.test.apiResults[6].data {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) { - keys = keys || []; +Ox.api = function(items, options) { + + 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) { var key = condition.key, 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 Ox.starts(a, b); }, '$': function(a, b) { return Ox.ends(a, b); }, }; - if (Ox.isString(value)) { - value = value.toLowerCase(); - } if (Ox.isString(itemValue)) { itemValue = itemValue.toLowerCase(); } + if (enums[key] && ( + operator.indexOf('<') > -1 + || operator.indexOf('>') > -1 + )) { + itemValue = enums[key].indexOf(itemValue); + } return test[operator](itemValue, value) == !not; } + function testQuery(item, query) { var match = true; Ox.forEach(query.conditions, function(condition) { @@ -128,22 +214,55 @@ Ox.api = function(items, keys) { }); return match; } + return function(options) { var data, - result = {data: {}, status: {code: 200, text: 'ok'}}; + result = {data: {}, status: {code: 200, text: 'ok'}}, + sort = {}; 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 = Ox.clone(items); } - if (options.keys) { - if (options.sort) { - result.data.items = Ox.sortBy(result.data.items, options.sort); - } + if (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)) { + // filter keys + if (options.keys.indexOf(unique) == -1) { + options.keys.push(unique); + } result.data.items = result.data.items.map(function(item) { var ret = {}; options.keys.forEach(function(key) { @@ -153,21 +272,15 @@ Ox.api = function(items, keys) { }); } if (options.range) { + // apply range result.data.items = Ox.sub( 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; }; + }; /*@ @@ -259,8 +372,11 @@ Ox.range = function() { len, matches = {}, sort = {}; // find leading numbers arr.forEach(function(val, i) { - var match = /^\d+/.exec(arr_[i]); - matches[val] = match ? match[0] : ''; + var match; + if (Ox.isString(val)) { + match = /^\d+/.exec(arr_[i]); + matches[val] = match ? match[0] : ''; + } }); // get length of longest leading number len = Ox.max(Ox.map(matches, function(val) { @@ -269,13 +385,15 @@ Ox.range = function() { // pad leading numbers, make lowercase, // and remove leading non-word characters arr.forEach(function(val, i) { - sort[val] = ( - matches[val] - ? arr_[i].toString().replace( - matches[val], Ox.pad(matches[val], len) - ) - : arr_[i] - ).toLowerCase().replace(/^\W+/, ''); + sort[val] = Ox.isString(arr_[i]) + ? ( + matches[val] + ? arr_[i].replace( + matches[val], Ox.pad(matches[val], len) + ) + : arr_[i] + ).toLowerCase().replace(/^\W+/, '') + : arr_[i]; }); return sort; } @@ -320,10 +438,10 @@ Ox.range = function() { Ox.sortBy = function(arr, by, fn) { var length = by.length, values = {}; by = by.map(function(v) { - return { + return Ox.isString(v) ? { key: v.replace(/^[\+\-]/, ''), operator: v[0] == '-' ? '-' : '+' - }; + } : v; }); fn = fn || {}; by.map(function(v) { diff --git a/source/Ox/js/String.js b/source/Ox/js/String.js index a3c05e92..68a969a4 100644 --- a/source/Ox/js/String.js +++ b/source/Ox/js/String.js @@ -46,6 +46,8 @@ Ox.endsWith Checks if a string ends with a given substring @*/ Ox.ends = Ox.endsWith = function(str, sub) { // fixme: rename to ends + str = str.toString(); + sub = sub.toString(); return str.substr(str.length - sub.length) == sub; }; @@ -427,6 +429,8 @@ Ox.startsWith Checks if a string starts with a given substring @*/ Ox.starts = Ox.startsWith = function(str, sub) { // fixme: rename to starts + str = str.toString(); + sub = sub.toString(); return str.substr(0, sub.length) == sub; };