// vim: et:ts=4:sw=4:sts=4:ft=javascript pandora.Query = (function() { function constructFind(query) { return /*encodeURI(*/query.conditions.map(function(condition) { var ret; if (condition.conditions) { ret = '[' + constructFind(condition) + ']'; } else { ret = condition.value !== '' ? condition.key + (condition.key ? ':' : '') + constructValue(condition.value, condition.operator) : null; } return ret; }).join(query.operator == '&' ? ',' : '|')/*)*/; } function constructValue(value, operator) { operator = operator.replace('=', '^$'); if (operator.indexOf('$') > -1) { value = operator.substr(0, operator.length - 1) + value + '$'; } else { value = operator + value; } return value; } function everyCondition(conditions, key, operator) { // if every condition has the given key and operator // (excluding conditions where all subconditions match) // returns true, otherwise false return Ox.every(conditions, function(condition) { return condition.key == key && condition.operator == operator; }); } function getGroupsData(fullQuery) { // a group is selected if exactly one condition in an & query // or every condition in an | query // has the group id as key and "=" as operator return pandora.user.ui.groups.map(function(key) { var index = -1, query = Ox.clone(fullQuery, true), selected = []; if (query.operator == '|') { if (everyCondition(query.conditions, key, '=')) { index = Ox.range(query.conditions.length); selected = query.conditions.map(function(condition) { return condition.value; }); } } else { index = oneCondition(query.conditions, key, '='); if (index > -1) { selected = query.conditions[index].conditions ? query.conditions[index].conditions.map(function(condition) { return condition.value; }) : [query.conditions[index].value]; } } if (selected.length) { if (Ox.isArray(index)) { query = {conditions: [], operator: ''}; } else { query.conditions.splice(index, 1); if (query.conditions.length == 1) { if (query.conditions[0].conditions) { // unwrap single remaining bracketed query query = { conditions: query.conditions[0].conditions, operator: query.conditions[0].operator } } else { query.operator = ''; } } } } return { index: index, query: query, selected: selected }; }); } function parseFind(str) { // takes a find query string, returns useful information about the application's state // (selected lists, find input key/value (and index of the corresponding condition), query object) var conditions, index, indices, ret = { find: {index: -1, key: '', value: ''}, groups: [], // {index, query, selected} list: '', query: {conditions: [], operator: ''} }, subconditions = str.match(/\[.*?\]/g) || []; if (str.length) { // replace subconditions with placeholder, // so we can later split by main operator subconditions.forEach(function(subcondition, i) { subconditions[i] = subcondition.substr(1, subcondition.length - 2); str = str.replace(subconditions[i], i); }); if (str.indexOf(',') > -1) { ret.query.operator = '&'; } else if (str.indexOf('|') > -1) { ret.query.operator = '|'; } ret.query.conditions = ( ret.query.operator == '' ? [str] : str.split(ret.query.operator == '&' ? ',' : '|') ).map(function(condition, i) { var kv, ret; if (condition[0] == '[') { // re-insert subcondition ret = parseFind(subconditions[parseInt(condition.substr(1, condition.length - 2))]).query; } else { kv = ((condition.indexOf(':') > -1 ? '' : ':') + condition).split(':'); ret = Ox.extend({key: kv[0]}, parseValue(kv[1])); } return ret; }); // a list is selected if exactly one condition in an & query // has "list" as key and "" as operator if (ret.query.operator != '|') { index = oneCondition(ret.query.conditions, 'list', ''); if (index > -1 && !ret.query.conditions[index].conditions) { ret.list = ret.query.conditions[index].value; } } // find is populated if exactly one condition in an & query // has a findKey as key and "" as operator // (and all other conditions are either list or groups) // or if all conditions in an | query have the same group id as key if (ret.query.operator == '|') { ret.find = {index: -1, key: 'advanced', value: ''}; Ox.map(pandora.user.ui.groups, function(key) { if (everyCondition(ret.query.conditions, key, '=')) { ret.find.key = ''; return false; } }); } else { // number of conditions that are not list or groups conditions = ret.query.conditions.length - (ret.list != '') - ret.groups.filter(function(group) { return group.index > -1; }).length; // indices of non-advanced find queries indices = Ox.map(pandora.site.findKeys, function(findKey) { var key = findKey.id == 'all' ? '' : findKey.id, index = oneCondition(ret.query.conditions, key, ''); return index > -1 ? index : null; }); if (conditions > 0 || indices.length > 0) { ret.find = ( conditions == 1 && indices.length == 1 && !ret.query.conditions[indices[0]].conditions ) ? { index: indices[0], key: ret.query.conditions[indices[0]].key, value: ret.query.conditions[indices[0]].value } : {index: -1, key: 'advanced', value: ''} } } } ret.groups = getGroupsData(ret.query); return ret; } function oneCondition(conditions, key, operator) { // if exactly one condition has the given key and operator // (including conditions where all subconditions match) // returns the corresponding index, otherwise returns -1 var indices = Ox.map(conditions, function(condition, i) { return ( condition.conditions ? everyCondition(condition.conditions, key, operator) : condition.key == key && condition.operator == operator ) ? i : null; }); return indices.length == 1 ? indices[0] : -1; } function parseValue(str) { var value = { value: decodeURI(str), operator: '' }; if (value.value[0] == '!') { value.operator = '!'; value.value = value.value.substr(1); } if ('^<>'.indexOf(value.value[0]) > -1) { value.operator += value.value[0]; value.value = value.value.substr(1); } if (value.value.substr(-1) == '$') { value.operator += '$'; value.value = value.value.substr(0, value.value.length - 1); } value.operator = value.operator.replace('^$', '='); return value; } return { fromString: function(str) { var data query = Ox.unserialize(str.substr(1)), sort = [] if ('find' in query) { data = parseFind(query.find || ''); Ox.print(Ox.repeat('-', 120)); Ox.print('STATE', data); Ox.print(Ox.repeat('-', 120)); !pandora.user.ui.lists[data.list] && pandora.UI.set( ['lists', data.list].join('|'), pandora.site.user.ui.lists[''] ); pandora.UI.set({list: data.list}); pandora.user.ui.find = data.find; pandora.user.ui.groupsData = data.groups; Ox.print("PUUGD", pandora.user.ui.groupsData); pandora.user.ui.query = data.query; } if ('sort' in query) { sort = query.sort.split(','); pandora.UI.set(['lists', pandora.user.ui.list, 'sort'].join('|'), query.sort.split(',').map(function(v) { var hasOperator = '+-'.indexOf(v[0]) > -1, key = hasOperator ? v.substr(1) : v, operator = hasOperator ? v[0]/*.replace('+', '')*/ : pandora.getSortOperator(key); return { key: key, operator: operator }; })); } if ('view' in query) { pandora.UI.set(['lists', pandora.user.ui.list, 'listView'].join('|'), query.view); } }, /* toObject: function(groupId) { //Ox.print('tO', pandora.user.ui.findQuery.conditions) // the inner $.merge() creates a clone var conditions = $.merge( $.merge([], pandora.user.ui.listQuery.conditions), pandora.user.ui.findQuery.conditions ), operator; $.merge(conditions, pandora.user.queryGroups ? $.map(pandora.user.queryGroups, function(v, i) { if (v.id != groupId && v.query.conditions.length) { return v.query.conditions.length == 1 ? v.query.conditions : v.query; } }) : []); operator = conditions.length < 2 ? '' : ','; // fixme: should be & //Ox.print('>>', groupId, pandora.user.ui.find, conditions); return { conditions: conditions, operator: operator }; }, */ toString: function() { //Ox.print('tS', pandora.user.ui.find) var sort = pandora.user.ui.lists[pandora.user.ui.list].sort[0], key = sort.key, operator = sort.operator; return '?' + Ox.serialize({ find: constructFind(pandora.user.ui.query), sort: (operator == pandora.getSortOperator(key) ? '' : operator) + key, view: pandora.user.ui.lists[pandora.user.ui.list].listView }); }, updateGroups: function() { pandora.user.ui.groupsData = getGroupsData(pandora.user.ui.query); } }; })();