sao-base/src/common.js

1355 lines
51 KiB
JavaScript
Raw Normal View History

/* This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. */
2013-01-04 21:29:48 +01:00
(function() {
'use strict';
2013-01-04 21:29:48 +01:00
Sao.common = {};
2013-04-18 13:07:21 +02:00
Sao.common.BACKSPACE_KEYCODE = 8;
Sao.common.TAB_KEYCODE = 9;
Sao.common.RETURN_KEYCODE = 13;
Sao.common.DELETE_KEYCODE = 46;
Sao.common.F2_KEYCODE = 113;
Sao.common.F3_KEYCODE = 114;
2013-02-06 17:33:27 +01:00
Sao.common.compare = function(arr1, arr2) {
if (arr1.length != arr2.length) {
return false;
}
for (var i = 0; i < arr1.length; i++) {
if (arr1[i] instanceof Array && arr2[i] instanceof Array) {
if (!Sao.common.compare(arr1[i], arr2[i])) {
return false;
}
} else if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
2013-02-06 17:33:27 +01:00
};
2013-04-08 19:15:49 +02:00
// Find the intersection of two arrays.
// The arrays must be sorted.
Sao.common.intersect = function(a, b) {
var ai = 0, bi = 0;
var result = [];
while (ai < a.length && bi < b.length) {
if (a[ai] < b[bi]) {
ai++;
} else if (a[ai] > b[bi]) {
bi++;
} else {
result.push(a[ai]);
ai++;
bi++;
}
}
return result;
};
2013-01-04 21:29:48 +01:00
Sao.common.selection = function(title, values, alwaysask) {
if (alwaysask === undefined) {
alwaysask = false;
}
2013-04-17 11:42:12 +02:00
var prm = jQuery.Deferred();
2013-01-04 21:29:48 +01:00
if ((Object.keys(values).length == 1) && (!alwaysask)) {
var key = Object.keys(values)[0];
prm.resolve(values[key]);
return prm;
}
// TODO
2013-04-17 11:42:12 +02:00
return prm.fail();
2013-01-04 21:29:48 +01:00
};
Sao.common.date_format = function() {
// TODO
// http://stackoverflow.com/questions/2678230/how-to-getting-browser-current-locale-preference-using-javascript
return '%m/%d/%Y';
};
Sao.common.text_to_float_time = function(text, conversion, digit) {
// TODO
return text;
};
2013-08-21 16:26:20 +02:00
Sao.common.ModelAccess = Sao.class_(Object, {
init: function() {
this.batchnum = 100;
this._access = {};
},
load_models: function(refresh) {
var prm = jQuery.Deferred();
if (!refresh) {
this._access = {};
}
Sao.rpc({
'method': 'model.ir.model.list_models',
'params': [{}]
}, Sao.Session.current_session).then(function(models) {
var deferreds = [];
var update_access = function(access) {
this._access = jQuery.extend(this._access, access);
};
for (var i = 0; i < models.length; i += this.batchnum) {
var to_load = models.slice(i, i + this.batchnum);
deferreds.push(Sao.rpc({
'method': 'model.ir.model.access.get_access',
'params': [to_load, {}]
}, Sao.Session.current_session)
.then(update_access.bind(this)));
}
jQuery.when.apply(jQuery, deferreds).then(
prm.resolve, prm.reject);
}.bind(this));
return prm;
},
get: function(model) {
return this._access[model];
}
});
Sao.common.MODELACCESS = new Sao.common.ModelAccess();
2013-08-16 13:35:39 +02:00
Sao.common.humanize = function(size) {
var sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
for (var i =0, len = sizes.length; i < len; i++) {
if (size < 1000) {
return size.toPrecision(4) + ' ' + sizes[i];
}
size /= 1000;
}
};
Sao.common.EvalEnvironment = function(parent_, eval_type) {
if (eval_type === undefined)
eval_type = 'eval';
var environment;
if (eval_type == 'eval') {
environment = parent_.get_eval();
} else {
environment = {};
2013-04-08 16:55:00 +02:00
for (var key in parent_.model.fields) {
var field = parent_.model.fields[key];
environment[key] = field.get_on_change_value(parent_);
}
}
environment.id = parent_.id;
if (parent_.group.parent)
2013-02-04 18:29:15 +01:00
Object.defineProperty(environment, '_parent_' +
parent_.group.parent_name, {
'enumerable': true,
2013-02-04 18:29:15 +01:00
'get': function() {
return Sao.common.EvalEnvironment(parent_.group.parent,
2013-02-04 18:29:15 +01:00
eval_type);
}
});
environment.get = function(item, default_) {
if (this.hasOwnProperty(item))
return this[item];
return default_;
};
return environment;
};
Sao.common.selection_mixin = {};
Sao.common.selection_mixin.init = function() {
this.selection = null;
this._last_domain = null;
this._values2selection = {};
this._domain_cache = {};
};
Sao.common.selection_mixin.init_selection = function(key, callback) {
if (!key) {
key = [];
(this.attributes.selection_change_with || []).forEach(function(e) {
key.push([e, null]);
});
key.sort();
}
var selection = jQuery.extend([], this.attributes.selection || []);
var prepare_selection = function(selection) {
if (this.attributes.sort === undefined || this.attributes.sort) {
selection.sort(function(a, b) {
return a[1].localeCompare(b[1]);
});
}
this.selection = jQuery.extend([], selection);
if (callback) callback(this.selection);
};
if (!(selection instanceof Object) &&
!(key in this._values2selection)) {
var prm;
if (key) {
var params = {};
key.forEach(function(e) {
params[e[0]] = e[1];
});
prm = this.model.execute(selection, [params], {});
} else {
prm = this.model.execute(selection, [], {});
}
prm.pipe(prepare_selection.bind(this));
prm.pipe(this.set_selection.bind(this));
} else {
if (key in this._values2selection) {
selection = this._values2selection.selection;
}
prepare_selection.call(this, selection);
}
};
Sao.common.selection_mixin.update_selection = function(record, field,
callback) {
if (!field) {
return;
}
if (!('relation' in this.attributes)) {
var change_with = this.attributes.selection_change_with || [];
var key = [];
var args = record._get_on_change_args(change_with);
for (var k in args) {
key.push([k, args[k]]);
}
key.sort();
Sao.common.selection_mixin.init_selection.call(this, key,
callback);
} else {
var domain = field.get_domain(record);
var jdomain = JSON.stringify(domain);
if (jdomain in this._domain_cache) {
this.selection = this._domain_cache[jdomain];
this._last_domain = domain;
}
2013-05-16 18:25:18 +02:00
if ((this._last_domain !== null) &&
Sao.common.compare(domain, this._last_domain)) {
return;
}
var prm = Sao.rpc({
'method': 'model.' + this.attributes.relation + '.search_read',
'params': [domain, 0, null, null, ['rec_name'], {}]
}, record.model.session);
prm.done(function(result) {
var selection = [];
result.forEach(function(x) {
selection.push([x.id, x.rec_name]);
});
selection.push([null, '']);
this._last_domain = domain;
this._domain_cache[jdomain] = selection;
this.selection = jQuery.extend([], selection);
if (callback) {
callback(this.selection);
}
}.bind(this));
prm.fail(function() {
this._last_domain = null;
this.selection = [];
if (callback) {
callback(this.selection);
}
}.bind(this));
}
};
2013-04-18 17:25:23 +02:00
2013-05-02 17:23:37 +02:00
Sao.common.Button = Sao.class_(Object, {
init: function(attributes) {
this.attributes = attributes;
this.el = jQuery('<button/>').button({
text: true,
label: attributes.string || ''
});
// TODO icon
},
set_state: function(record) {
var states;
if (record) {
states = record.expr_eval(this.attributes.states || {});
} else {
states = {};
}
if (states.invisible) {
this.el.hide();
} else {
this.el.show();
}
this.el.prop('disabled', states.readonly);
// TODO icon
if (record) {
var parent = record.group.parent;
while (parent) {
if (parent.has_changed()) {
this.el.prop('disabled', false);
break;
}
parent = parent.group.parent;
}
}
2013-05-02 17:23:37 +02:00
}
});
2013-05-16 18:50:15 +02:00
Sao.common.udlex = Sao.class_(Object, {
init: function(instream) {
var Stream = Sao.class_(Object, {
init: function(stream) {
this.stream = stream.split('');
this.i = 0;
},
read: function(length) {
if (length === undefined) {
length = 1;
}
if (this.i >= this.stream.length) {
return null;
}
var value = this.stream
.slice(this.i, this.i + length).join();
this.i += length;
return value;
}
});
this.instream = new Stream(instream);
this.eof = null;
this.commenters = '';
this.nowordchars = [':', '>', '<', '=', '!', '"', ';', '(', ')'];
this.whitespace = ' \t\r\n';
this.whitespace_split = false;
this.quotes = '"';
this.escape = '\\';
this.escapedquotes = '"';
this.state = ' ';
this.pushback = [];
this.token = '';
},
get_token: function() {
if (this.pushback.length > 0) {
return this.pushback.shift();
}
var raw = this.read_token();
return raw;
},
read_token: function() {
var quoted = false;
var escapedstate = ' ';
while (true) {
var nextchar = this.instream.read(1);
if (this.state === null) {
this.token = ''; // past en of file
break;
} else if (this.state == ' ') {
if (!nextchar) {
this.state = null; // end of file
break;
} else if (this.whitespace.contains(nextchar)) {
if (this.token || quoted) {
break; // emit current token
} else {
continue;
}
} else if (this.commenters.contains(nextchar)) {
// TODO readline
} else if (this.escape.contains(nextchar)) {
escapedstate = 'a';
this.state = nextchar;
} else if (!~this.nowordchars.indexOf(nextchar)) {
2013-05-16 18:50:15 +02:00
this.token = nextchar;
this.state = 'a';
} else if (this.quotes.contains(nextchar)) {
this.state = nextchar;
} else if (this.whitespace_split) {
this.token = nextchar;
this.state = 'a';
} else {
this.token = nextchar;
if (this.token || quoted) {
break; // emit current token
} else {
continue;
}
}
} else if (this.quotes.contains(this.state)) {
quoted = true;
if (!nextchar) { // end of file
throw 'no closing quotation';
}
if (nextchar == this.state) {
this.state = 'a';
} else if (this.escape.contains(nextchar) &&
this.escapedquotes.contains(this.state)) {
escapedstate = this.state;
this.state = nextchar;
} else {
this.token = this.token + nextchar;
}
} else if (this.escape.contains(this.state)) {
if (!nextchar) { // end of file
throw 'no escaped character';
}
if (this.quotes.contains(escapedstate) &&
(nextchar != this.state) &&
(nextchar != escapedstate)) {
this.token = this.token + this.state;
}
this.token = this.token + nextchar;
this.state = escapedstate;
} else if (this.state == 'a') {
if (!nextchar) {
this.state = null; // end of file
break;
} else if (this.whitespace.contains(nextchar)) {
this.state = ' ';
if (this.token || quoted) {
break; // emit current token
} else {
continue;
}
} else if (this.commenters.contains(nextchar)) {
// TODO
} else if (this.quotes.contains(nextchar)) {
this.state = nextchar;
} else if (this.escape.contains(nextchar)) {
escapedstate = 'a';
this.state = nextchar;
} else if ((!~this.nowordchars.indexOf(nextchar)) ||
2013-05-16 18:50:15 +02:00
this.quotes.contains(nextchar) ||
this.whitespace_split) {
this.token = this.token + nextchar;
} else {
this.pushback.unshift(nextchar);
this.state = ' ';
if (this.token) {
break; // emit current token
} else {
continue;
}
}
}
}
var result = this.token;
this.token = '';
if (!quoted && result === '') {
result = null;
}
return result;
},
next: function() {
var token = this.get_token();
if (token == this.eof) {
return null;
}
return token;
}
});
Sao.common.DomainParser = Sao.class_(Object, {
OPERATORS: ['!=', '<=', '>=', '=', '!', '<', '>'],
init: function(fields) {
this.fields = {};
this.strings = {};
for (var name in fields) {
var field = fields[name];
if (field.searchable || (field.searchable === undefined)) {
this.fields[name] = field;
this.strings[field.string.toLowerCase()] = field;
}
}
},
parse: function(input) {
try {
var lex = new Sao.common.udlex(input);
var tokens = [];
while (true) {
var token = lex.next();
if (token === null) {
break;
}
tokens.push(token);
}
tokens = this.group_operator(tokens);
tokens = this.parenthesize(tokens);
tokens = this.group(tokens);
tokens = this.operatorize(tokens, 'or');
tokens = this.operatorize(tokens, 'and');
tokens = this.parse_clause(tokens);
return this.simplify(tokens);
} catch (e) {
if (e == 'no closing quotation') {
return this.parse(input + '"');
}
throw e;
}
},
string: function(domain) {
var string = function(clause) {
if (jQuery.isEmptyObject(clause)) {
return '';
}
var escaped;
if ((typeof clause[0] == 'string') &&
((clause[0] in this.fields) ||
(clause[0] == 'rec_name'))) {
var name = clause[0];
var operator = clause[1];
var value = clause[2];
if (!(name in this.fields)) {
escaped = value.replace('%%', '__');
if (escaped.startsWith('%') && escaped.endsWith('%')) {
value = value.slice(1, -1);
}
return this.quote(value);
}
var field = this.fields[name];
if (operator.contains('ilike')) {
escaped = value.replace('%%', '__');
if (escaped.startsWith('%') && escaped.endsWith('%')) {
value = value.slice(1, -1);
} else if (!escaped.contains('%')) {
if (operator == 'ilike') {
operator = '=';
} else {
operator = '!';
}
value = value.replace('%%', '%');
}
}
var def_operator = this.default_operator(field);
if ((def_operator == operator.trim()) ||
(operator.contains(def_operator) &&
operator.contains('not'))) {
operator = operator.replace(def_operator, '')
.replace('not', '!').trim();
}
if (operator.endsWith('in')) {
if (operator == 'not in') {
operator = '!';
} else {
operator = '';
}
}
var formatted_value = this.format_value(field, value);
if (~this.OPERATORS.indexOf(operator) &&
~['char', 'text', 'sha', 'selection']
.indexOf(field.type) &&
2013-05-16 18:50:15 +02:00
(value === '')) {
formatted_value = '""';
}
return (this.quote(field.string) + ': ' +
operator + formatted_value);
} else {
return '(' + this.string(clause) + ')';
}
};
string = string.bind(this);
if (jQuery.isEmptyObject(domain)) {
return '';
}
var nary = ' ';
if ((domain[0] == 'AND') || (domain[0] == 'OR')) {
if (domain[0] == 'OR') {
nary = ' or ';
}
domain = domain.slice(1);
}
return domain.map(string).join(nary);
},
group_operator: function(tokens) {
var cur = tokens[0];
var nex = null;
var result = [];
tokens.slice(1).forEach(function(nex) {
if ((nex == '=') && cur &&
~this.OPERATORS.indexOf(cur + nex)) {
2013-05-16 18:50:15 +02:00
result.push(cur + nex);
cur = null;
} else {
if (cur !== null) {
result.push(cur);
}
cur = nex;
}
}.bind(this));
if (cur !== null) {
result.push(cur);
}
return result;
},
parenthesize: function(tokens) {
var result = [];
var current = result;
var parent = [];
tokens.forEach(function(token, i) {
if (current === undefined) {
return;
}
if (token == '(') {
parent.push(current);
current = current[current.push([]) - 1];
} else if (token == ')') {
current = parent.pop();
} else {
current.push(token);
}
});
return result;
},
group: function(tokens) {
var result = [];
var _group = function(parts) {
var result = [];
var push_result = function(part) {
result.push([part]);
};
var i = parts.indexOf(':');
if (!~i) {
2013-05-16 18:50:15 +02:00
parts.forEach(push_result);
return result;
}
var sub_group = function(name, lvalue) {
return function(part) {
if (!jQuery.isEmptyObject(name)) {
if (!jQuery.isEmptyObject(lvalue)) {
if (part[0] !== null) {
lvalue.push(part[0]);
}
result.push(name.concat([lvalue]));
} else {
result.push(name.concat(part));
}
name.splice(0, name.length);
} else {
result.push(part);
}
};
};
for (var j = 0; j < i; j++) {
var name = parts.slice(j, i).join(' ');
if (name.toLowerCase() in this.strings) {
if (!jQuery.isEmptyObject(parts.slice(0, j))) {
parts.slice(0, j).forEach(push_result);
} else {
push_result(null);
}
name = [name];
if (((i + 1) < parts.length) &&
(~this.OPERATORS.indexOf(parts[i + 1]))) {
2013-05-16 18:50:15 +02:00
name = name.concat([parts[i + 1]]);
i += 1;
} else {
name = name.concat([null]);
}
var lvalue = [];
while ((i + 2) < parts.length) {
if (parts[i + 2] == ';') {
lvalue.push(parts[i + 1]);
i += 2;
} else {
break;
}
}
_group(parts.slice(i + 1)).forEach(
sub_group(name, lvalue));
if (!jQuery.isEmptyObject(name)) {
if (!jQuery.isEmptyObject(lvalue)) {
result.push(name.concat([lvalue]));
} else {
result.push(name.concat([null]));
}
}
break;
}
}
return result;
};
_group = _group.bind(this);
var parts = [];
tokens.forEach(function(token) {
if (token instanceof Array) {
_group(parts).forEach(function(group) {
if (!Sao.common.compare(group, [null])) {
result.push(group);
}
});
parts = [];
result.push(this.group(token));
} else {
parts.push(token);
}
}.bind(this));
_group(parts).forEach(function(group) {
if (!Sao.common.compare(group, [null])) {
result.push(group);
}
});
return result;
},
operatorize: function(tokens, operator) {
var result = [];
operator = operator || 'or';
tokens = jQuery.extend([], tokens);
var test = function(value) {
if (value instanceof Array) {
return Sao.common.compare(value, [operator]);
} else {
return value == operator;
}
};
var cur = tokens.shift();
while (test(cur)) {
cur = tokens.shift();
}
if (cur === undefined) {
return result;
}
if (cur instanceof Array) {
cur = this.operatorize(cur, operator);
}
var nex = null;
while (!jQuery.isEmptyObject(tokens)) {
nex = tokens.shift();
if ((nex instanceof Array) && !test(nex)) {
nex = this.operatorize(nex, operator);
}
if (test(nex)) {
nex = tokens.shift();
while (test(nex)) {
nex = tokens.shift();
}
if (nex instanceof Array) {
nex = this.operatorize(nex, operator);
}
if (nex !== undefined) {
cur = [operator.toUpperCase(), cur, nex];
} else {
if (!test(cur)) {
result.push([operator.toUpperCase(), cur]);
cur = null;
}
}
nex = null;
} else {
if (!test(cur)) {
result.push(cur);
}
cur = nex;
}
}
if (jQuery.isEmptyObject(tokens)) {
if ((nex !== null) && !test(nex)) {
result.push(nex);
} else if ((cur !== null) && !test(nex)) {
result.push(cur);
}
}
return result;
},
parse_clause: function(tokens) {
var result = [];
tokens.forEach(function(clause) {
if ((clause == 'OR') || (clause == 'AND')) {
result.push(clause);
} else if ((clause.length == 1) &&
!(clause[0] instanceof Array)) {
result.push(['rec_name', 'ilike', this.likify(clause[0])]);
} else if ((clause.length == 3) &&
(clause[0].toLowerCase() in this.strings)) {
var name = clause[0];
var operator = clause[1];
var value = clause[2];
var field = this.strings[clause[0].toLowerCase()];
if (operator === null) {
operator = this.default_operator(field);
}
if (value instanceof Array) {
if (operator == '!') {
operator = 'not in';
} else {
operator = 'in';
}
}
if (operator == '!') {
operator = this.negate_operator(
this.default_operator(field));
}
if (operator.contains('like')) {
value = this.likify(value);
}
if (~['integer', 'float', 'numeric', 'datetime', 'date',
'time'].indexOf(field.type)) {
2013-05-16 18:50:15 +02:00
if (value && value.contains('..')) {
var values = value.split('..', 2);
var lvalue = this.convert_value(field, values[0]);
var rvalue = this.convert_value(field, values[1]);
result.push([
[field.name, '>=', lvalue],
[field.name, '<', rvalue]
]);
return;
}
}
if (value instanceof Array) {
value = value.map(function(v) {
return this.convert_value(field, v);
}.bind(this));
} else {
value = this.convert_value(field, value);
}
result.push([field.name, operator, value]);
} else {
result.push(this.parse_clause(clause));
}
}.bind(this));
return result;
},
likify: function(value) {
if (!value) {
return '%';
}
var escaped = value.replace('%%', '__');
if (escaped.contains('%')) {
return value;
} else {
return '%' + value + '%';
}
},
quote: function(value) {
if (typeof value != 'string') {
return value;
}
var tests = [':', ' ', '(', ')'].concat(this.OPERATORS);
for (var i = 0; i < tests.length; i++) {
var test = tests[i];
if (value.contains(test)) {
return '"' + value + '"';
}
}
return value;
},
default_operator: function(field) {
if (~['char', 'text', 'many2one', 'many2many', 'one2many']
.indexOf(field.type)) {
2013-05-16 18:50:15 +02:00
return 'ilike';
} else {
return '=';
}
},
negate_operator: function(operator) {
switch (operator) {
case 'ilike':
return 'not ilike';
case '=':
return '!=';
case 'in':
return 'not in';
}
},
convert_value: function(field, value) {
var convert_selection = function() {
if (typeof value == 'string') {
for (var i = 0; i < field.selection.length; i++) {
var selection = field.selection[i];
var key = selection[0];
var text = selection[1];
if (value.toLowerCase() == text.toLowerCase()) {
return key;
}
}
}
return value;
};
var converts = {
'boolean': function() {
if (typeof value == 'string') {
return ['y', 'yes', 'true', 't', '1'].some(
function(test) {
return test.toLowerCase().startsWith(
value.toLowerCase());
});
} else {
return Boolean(value);
}
},
'float': function() {
var result = Number(value);
if (isNaN(result) || value === '' || value === null) {
return null;
} else {
return result;
}
},
'integer': function() {
var result = parseInt(value, 10);
if (isNaN(result)) {
return null;
} else {
return result;
}
},
'numeric': function() {
var result = new Sao.Decimal(value);
if (isNaN(result.valueOf()) ||
value === '' || value === null) {
return null;
} else {
return result;
}
},
'selection': convert_selection,
'reference': convert_selection,
// TODO datetime
// TODO date
// TODO time
'many2one': function() {
if (value === '') {
return null;
} else {
return value;
}
}
};
var func = converts[field.type];
if (func) {
return func();
} else {
return value;
}
},
format_value: function(field, value) {
var format_float = function() {
if (!value && value !== 0 && value !== new Sao.Decimal(0)) {
return '';
}
var digit = parseInt((field.digits || [16, 2])[1], 10);
if (isNaN(digit)) {
digit = 2;
}
return value.toFixed(digit);
};
var format_selection = function() {
for (var i = 0; i < field.selection.length; i++) {
if (field.selection[i][0] == value) {
return field.selection[i][1];
}
}
return value || '';
};
var converts = {
'boolean': function() {
if (value) {
return 'True'; // TODO translate
} else {
return 'False';
}
},
'integer': function() {
if (value || value === 0) {
return '' + parseInt(value, 10);
} else {
return '';
}
},
'float': format_float,
'numeric': format_float,
'selection': format_selection,
'reference': format_selection,
// TODO datetime
// TODO date
// TODO time
'many2one': function() {
if (value === null) {
return '';
} else {
return value;
}
}
};
if (value instanceof Array) {
return value.map(function(v) {
return this.format_value(field, v);
}.bind(this)).join(';');
} else {
var func = converts[field.type];
if (func) {
return this.quote(func(value));
} else if (value === null) {
return '';
} else {
return this.quote(value);
}
}
},
simplify: function(value) {
if (value instanceof Array) {
if ((value.length == 1) && (value[0] instanceof Array) &&
((value[0][0] == 'AND') || (value[0][0] == 'OR') ||
(value[0][0] instanceof Array))) {
return this.simplify(value[0]);
} else if ((value.length == 2) &&
((value[0] == 'AND') || (value[0] == 'OR')) &&
(value[1] instanceof Array)) {
return this.simplify(value[1]);
} else if ((value.length == 3) &&
((value[0] == 'AND') || (value[0] == 'OR')) &&
(value[1] instanceof Array) &&
(value[0] == value[1][0])) {
value = this.simplify(value[1]).concat([value[2]]);
}
return value.map(this.simplify.bind(this));
}
return value;
}
});
2013-07-23 10:44:54 +02:00
Sao.common.DomainInversion = Sao.class_(Object, {
and: function(a, b) {return a && b;},
or: function(a, b) {return a || b;},
OPERATORS: {
'=': function(a, b) {
if ((a instanceof Array) && (b instanceof Array)) {
return Sao.common.compare(a, b);
} else {
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) {
if ((a instanceof Array) && (b instanceof Array)) {
return !Sao.common.compare(a, b);
} else {
return (a !== b);
}
},
'in': function(a, b) {
return Sao.common.DomainInversion.in_(a, b);
},
'not in': function(a, b) {
return !Sao.common.DomainInversion.in_(a, b);
},
// Those operators are not supported (yet ?)
'like': function() {return true;},
'ilike': function() {return true;},
'not like': function() {return true;},
'not ilike': function() {return true;},
'child_of': function() {return true;},
'not child_of': function() {return true;}
},
locale_part: function(expression, field_name) {
if (expression === field_name) {
return 'id';
}
if (expression.contains('.')) {
return expression.split('.').slice(1).join('.');
}
return expression;
},
is_leaf: function(expression) {
return ((expression instanceof Array) &&
(expression.length > 2) &&
(expression[1] in this.OPERATORS));
},
eval_leaf: function(part, context, boolop) {
if (boolop === undefined) {
boolop = this.and;
}
var field = part[0];
var operand = part[1];
var value = part[2];
if (field.contains('.')) {
// In the case where the leaf concerns a m2o then having a
// value in the evaluation context is deemed suffisant
return Boolean(context[field.split('.')[0]]);
}
if ((operand == '=') && !context[field] && (boolop === this.and)) {
// We should consider that other domain inversion will set a
// correct value to this field
return true;
}
var context_field = context[field];
if ((context_field instanceof Date) && !context_field) {
// TODO set value to min
}
if ((value instanceof Date) && !context_field) {
// TODO set context_field to min
}
if ((typeof context_field == 'string') &&
(value instanceof Array) && value.length == 2) {
value = value.join(',');
} else if ((context_field instanceof Array) &&
(typeof value == 'string') && context_field.length == 2) {
context_field = context_field.join(',');
}
return this.OPERATORS[operand](context_field, value);
},
inverse_leaf: function(domain) {
if (~['AND', 'OR'].indexOf(domain)) {
2013-07-23 10:44:54 +02:00
return domain;
} else if (this.is_leaf(domain)) {
if (domain[1].contains('child_of')) {
if (domain.length == 3) {
return domain;
} else {
return [domain[3]].concat(domain.slice(1));
}
}
return domain;
} else {
return domain.map(this.inverse_leaf.bind(this));
}
},
eval_domain: function(domain, context, boolop) {
if (boolop === undefined) {
boolop = this.and;
}
if (this.is_leaf(domain)) {
return this.eval_leaf(domain, context, boolop);
} else if (jQuery.isEmptyObject(domain) && boolop == this.and) {
return true;
} else if (jQuery.isEmptyObject(domain) && boolop == this.or) {
return false;
} else if (domain[0] == 'AND') {
return this.eval_domain(domain.slice(1), context);
} else if (domain[0] == 'OR') {
return this.eval_domain(domain.slice(1), context, this.or);
} else {
return boolop(this.eval_domain(domain[0], context),
this.eval_domain(domain.slice(1), context, boolop));
}
},
localize_domain: function(domain, field_name) {
if (~['AND', 'OR', true, false].indexOf(domain)) {
2013-07-23 10:44:54 +02:00
return domain;
} else if (this.is_leaf(domain)) {
if (domain[1].contains('child_of')) {
if (domain.length == 3) {
return domain;
} else {
return [domain[3]].concat(domain.slice(1, -1));
}
}
return [this.locale_part(domain[0], field_name)]
.concat(domain.slice(1));
} else {
return domain.map(function(e) {
return this.localize_domain(e, field_name);
}.bind(this));
}
},
unlocalize_domain: function(domain, fieldname) {
if (~['AND', 'OR', true, false].indexOf(domain)) {
2013-07-23 10:44:54 +02:00
return domain;
} else if (this.is_leaf(domain)) {
return [fieldname + '.' + domain[0]].concat(domain.slice(1));
} else {
return domain.map(function(e) {
return this.unlocalize_domain(e, fieldname);
}.bind(this));
}
},
simplify: function(domain) {
if (this.is_leaf(domain)) {
return domain;
} else if (~['OR', 'AND'].indexOf(domain)) {
2013-07-23 10:44:54 +02:00
return domain;
} else if ((domain instanceof Array) && (domain.length == 1) &&
(!this.is_leaf(domain[0]))) {
return this.simplify(domain[0]);
} else if ((domain instanceof Array) && (domain.length == 2) &&
~['AND', 'OR'].indexOf(domain[0])) {
2013-07-23 10:44:54 +02:00
return [this.simplify(domain[1])];
} else {
return domain.map(this.simplify.bind(this));
}
},
merge: function(domain, domoperator) {
if (jQuery.isEmptyObject(domain) ||
~['AND', 'OR'].indexOf(domain)) {
2013-07-23 10:44:54 +02:00
return [];
}
var domain_type = domain[0] == 'OR' ? 'OR' : 'AND';
if (this.is_leaf(domain)) {
return [domain];
} else if (domoperator === undefined) {
return [domain_type].concat(Array.concat.apply([],
domain.map(function(e) {
return this.merge(e, domain_type);
}.bind(this))));
} else if (domain_type == domoperator) {
return Array.concat.apply([], domain.map(function(e) {
return this.merge(e, domain_type);
}.bind(this)));
} else {
// without setting the domoperator
return [this.merge(domain)];
}
},
parse: function(domain) {
var And = Sao.common.DomainInversion.And;
var Or = Sao.common.DomainInversion.Or;
if (this.is_leaf(domain)) {
return domain;
} else if (jQuery.isEmptyObject(domain)) {
return new And([]);
} else if (domain[0] === 'OR') {
return new Or(domain.slice(1));
} else {
var begin = 0;
if (domain[0] === 'AND') {
begin = 1;
}
return new And(domain.slice(begin));
}
},
domain_inversion: function(domain, symbol, context) {
if (context === undefined) {
context = {};
}
var expression = this.parse(domain);
if (!~expression.variables.indexOf(symbol)) {
2013-07-23 10:44:54 +02:00
return true;
}
return expression.inverse(symbol, context);
}
});
Sao.common.DomainInversion.in_ = function(a, b) {
if (a instanceof Array) {
for (var i = 0, len = a.length; i < len; i++) {
if (~b.indexOf(a[i])) {
2013-07-23 10:44:54 +02:00
return true;
}
}
return false;
} else {
return Boolean(~b.indexOf(a));
2013-07-23 10:44:54 +02:00
}
};
Sao.common.DomainInversion.And = Sao.class_(Object, {
init: function(expressions) {
this.domain_inversion = new Sao.common.DomainInversion();
this.branches = expressions.map(this.domain_inversion.parse.bind(
this.domain_inversion));
this.variables = [];
for (var i = 0, len = this.branches.length; i < len; i++) {
var expression = this.branches[i];
if (this.domain_inversion.is_leaf(expression)) {
this.variables.push(this.base(expression[0]));
} else if (expression instanceof
Sao.common.DomainInversion.And) {
this.variables = this.variables.concat(
expression.variables);
}
}
},
base: function(expression) {
if (!expression.contains('.')) {
return expression;
} else {
return expression.split('.')[0];
}
},
inverse: function(symbol, context) {
var DomainInversion = Sao.common.DomainInversion;
var result = [];
for (var i = 0, len = this.branches.length; i < len; i++) {
var part = this.branches[i];
if (part instanceof DomainInversion.And) {
var part_inversion = part.inverse(symbol, context);
var evaluated = typeof part_inversion == 'boolean';
if (!evaluated) {
result.push(part_inversion);
} else if (part_inversion) {
continue;
} else {
return false;
}
} else if (this.domain_inversion.is_leaf(part) &&
(this.base(part[0]) === symbol)) {
result.push(part);
} else {
var field = part[0];
if ((!(field in context)) ||
((field in context) &&
this.domain_inversion.eval_leaf(part, context,
this.domain_inversion.and))) {
result.push(true);
} else {
return false;
}
}
}
result = result.filter(function(e) {
return e !== true;
});
if (jQuery.isEmptyObject(result)) {
return true;
} else {
return this.domain_inversion.simplify(result);
}
}
});
Sao.common.DomainInversion.Or = Sao.class_(Sao.common.DomainInversion.And, {
inverse: function(symbol, context) {
var DomainInversion = Sao.common.DomainInversion;
var result = [];
if (!~this.variables.indexOf(symbol) &&
!jQuery.isEmptyObject(this.variables.filter(function(e) {
2013-07-23 10:44:54 +02:00
return !(e in context);
}))) {
2013-07-23 10:44:54 +02:00
// In this case we don't know anything about this OR part, we
// consider it to be True (because people will have the
// constraint on this part later).
return true;
}
for (var i = 0, len = this.branches.length; i < len; i++) {
var part = this.branches[i];
if (part instanceof DomainInversion.And) {
var part_inversion = part.inverse(symbol, context);
var evaluated = typeof part_inversion == 'boolean';
if (!~this.variables.indexOf(symbol)) {
2013-07-23 10:44:54 +02:00
if (evaluated && part_inversion) {
return true;
}
continue;
}
if (!evaluated) {
result.push(part_inversion);
} else if (part_inversion) {
return true;
} else {
continue;
}
} else if (this.domain_inversion.is_leaf(part) &&
(this.base(part[0]) == symbol)) {
result.push(part);
} else {
var field = part[0];
field = this.base(field);
if ((field in context) &&
this.domain_inversion.eval_leaf(part, context,
this.domain_inversion.or)) {
return true;
} else if ((field in context) &&
!this.domain_inversion.eval_leaf(part, context,
this.domain_inversion.or)) {
result.push(false);
}
}
}
result = result.filter(function(e) {
return e !== false;
});
if (jQuery.isEmptyObject(result)) {
return false;
} else {
return this.domain_inversion.simplify(['OR'].concat(result));
}
}
});
2013-01-04 21:29:48 +01:00
}());