2012-12-31 14:14:52 +01:00
|
|
|
/* 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';
|
2012-12-31 14:14:52 +01:00
|
|
|
|
2013-01-04 21:29:48 +01:00
|
|
|
Sao.common = {};
|
2012-12-31 14:14:52 +01:00
|
|
|
|
2013-04-18 13:07:21 +02:00
|
|
|
Sao.common.BACKSPACE_KEYCODE = 8;
|
|
|
|
Sao.common.TAB_KEYCODE = 9;
|
|
|
|
Sao.common.RETURN_KEYCODE = 13;
|
2015-05-29 17:08:28 +02:00
|
|
|
Sao.common.ESC_KEYCODE = 27;
|
2014-03-28 14:37:36 +01:00
|
|
|
Sao.common.UP_KEYCODE = 38;
|
|
|
|
Sao.common.DOWN_KEYCODE = 40;
|
2013-04-18 13:07:21 +02:00
|
|
|
Sao.common.DELETE_KEYCODE = 46;
|
|
|
|
Sao.common.F2_KEYCODE = 113;
|
|
|
|
Sao.common.F3_KEYCODE = 114;
|
|
|
|
|
2013-10-18 17:09:10 +02:00
|
|
|
Sao.common.SELECTION_NONE = 1;
|
2015-05-22 18:27:44 +02:00
|
|
|
Sao.common.SELECTION_SINGLE = 2;
|
2013-10-18 17:09:10 +02:00
|
|
|
Sao.common.SELECTION_MULTIPLE = 3;
|
|
|
|
|
2014-09-19 17:09:43 +02:00
|
|
|
Sao.common.BIG_IMAGE_SIZE = Math.pow(10, 6);
|
|
|
|
|
2013-02-06 17:33:27 +01:00
|
|
|
Sao.common.compare = function(arr1, arr2) {
|
2013-05-16 18:24:35 +02:00
|
|
|
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-10-18 17:09:10 +02:00
|
|
|
Sao.common.contains = function(array1, array2) {
|
|
|
|
for (var i = 0; i < array1.length; i++) {
|
|
|
|
if (Sao.common.compare(array1[i], array2)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2015-09-19 18:20:34 +02:00
|
|
|
// Handle click and Return press event
|
|
|
|
// If one, the handler is executed at most once for both events
|
|
|
|
Sao.common.click_press = function(func, one) {
|
|
|
|
return function handler(evt) {
|
2015-10-02 17:17:36 +02:00
|
|
|
if (evt.type != 'keypress' ||
|
|
|
|
evt.which == Sao.common.RETURN_KEYCODE) {
|
2015-09-19 18:20:34 +02:00
|
|
|
if (one) {
|
|
|
|
jQuery(this).off('click keypress', null, handler);
|
|
|
|
}
|
|
|
|
return func(evt);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2015-03-02 23:17:49 +01:00
|
|
|
// Cartesian product
|
|
|
|
Sao.common.product = function(array, repeat) {
|
|
|
|
repeat = repeat || 1;
|
|
|
|
var pools = [];
|
|
|
|
var i = 0;
|
|
|
|
while (i < repeat) {
|
|
|
|
pools = pools.concat(array);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
var result = [[]];
|
|
|
|
pools.forEach(function(pool) {
|
|
|
|
var tmp = [];
|
|
|
|
result.forEach(function(x) {
|
|
|
|
pool.forEach(function(y) {
|
|
|
|
tmp.push(x.concat([y]));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
result = tmp;
|
|
|
|
});
|
|
|
|
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();
|
2015-09-13 10:36:18 +02:00
|
|
|
if (jQuery.isEmptyObject(values)) {
|
|
|
|
prm.fail();
|
|
|
|
return prm;
|
|
|
|
}
|
2015-03-27 14:46:59 +01:00
|
|
|
var keys = Object.keys(values).sort();
|
|
|
|
if ((keys.length == 1) && (!alwaysask)) {
|
|
|
|
var key = keys[0];
|
2013-01-04 21:29:48 +01:00
|
|
|
prm.resolve(values[key]);
|
|
|
|
return prm;
|
|
|
|
}
|
2015-03-27 14:46:59 +01:00
|
|
|
var dialog = new Sao.Dialog(
|
2015-05-29 10:44:10 +02:00
|
|
|
title || Sao.i18n.gettext('Your selection:'),
|
|
|
|
'selection-dialog');
|
2015-03-27 14:46:59 +01:00
|
|
|
|
|
|
|
keys.forEach(function(k, i) {
|
|
|
|
jQuery('<div/>', {
|
|
|
|
'class': 'checkbox'
|
|
|
|
}).append(jQuery('<label/>')
|
|
|
|
.append(jQuery('<input/>', {
|
|
|
|
'type': 'radio',
|
|
|
|
'name': 'selection',
|
|
|
|
'value': i
|
|
|
|
}))
|
|
|
|
.append(' ' + k))
|
|
|
|
.appendTo(dialog.body);
|
|
|
|
});
|
|
|
|
dialog.body.find('input').first().prop('checked', true);
|
|
|
|
|
|
|
|
jQuery('<button/>', {
|
|
|
|
'class': 'btn btn-link',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Cancel')).click(function() {
|
2015-03-27 14:46:59 +01:00
|
|
|
dialog.modal.modal('hide');
|
|
|
|
prm.fail();
|
|
|
|
}).appendTo(dialog.footer);
|
|
|
|
jQuery('<button/>', {
|
|
|
|
'class': 'btn btn-primary',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('OK')).click(function() {
|
2015-03-27 14:46:59 +01:00
|
|
|
var i = dialog.body.find('input:checked').attr('value');
|
|
|
|
dialog.modal.modal('hide');
|
|
|
|
prm.resolve(values[keys[i]]);
|
|
|
|
}).appendTo(dialog.footer);
|
|
|
|
dialog.modal.on('hidden.bs.modal', function(e) {
|
|
|
|
jQuery(this).remove();
|
|
|
|
});
|
|
|
|
dialog.modal.modal('show');
|
|
|
|
return prm;
|
2013-01-04 21:29:48 +01:00
|
|
|
};
|
2013-01-22 17:13:26 +01:00
|
|
|
|
2015-02-06 00:32:51 +01:00
|
|
|
Sao.common.moment_format = function(format) {
|
|
|
|
return format
|
|
|
|
.replace('%a', 'ddd')
|
|
|
|
.replace('%A', 'dddd')
|
|
|
|
.replace('%w', 'd')
|
|
|
|
.replace('%d', 'DD')
|
|
|
|
.replace('%b', 'MMM')
|
|
|
|
.replace('%B', 'MMMM')
|
|
|
|
.replace('%m', 'MM')
|
|
|
|
.replace('%y', 'YY')
|
|
|
|
.replace('%Y', 'YYYY')
|
|
|
|
.replace('%H', 'HH')
|
|
|
|
.replace('%I', 'hh')
|
|
|
|
.replace('%p', 'A')
|
|
|
|
.replace('%M', 'mm')
|
|
|
|
.replace('%S', 'ss')
|
|
|
|
.replace('%f', 'SSS')
|
|
|
|
.replace('%z', 'ZZ')
|
|
|
|
.replace('%Z', 'zz')
|
|
|
|
.replace('%j', 'DDDD')
|
|
|
|
.replace('%U', 'ww')
|
|
|
|
.replace('%W', 'WW')
|
|
|
|
.replace('%c', 'llll')
|
|
|
|
.replace('%x', 'L')
|
|
|
|
.replace('%X', 'LTS')
|
|
|
|
.replace('%', '%%')
|
|
|
|
;
|
|
|
|
};
|
|
|
|
|
2015-01-26 11:32:47 +01:00
|
|
|
Sao.common.date_format = function(format) {
|
|
|
|
if (jQuery.isEmptyObject(format) && Sao.Session.current_session) {
|
2013-10-04 11:21:32 +02:00
|
|
|
var context = Sao.Session.current_session.context;
|
|
|
|
if (context.locale && context.locale.date) {
|
2015-01-26 11:32:47 +01:00
|
|
|
format = context.locale.date;
|
2013-10-04 11:21:32 +02:00
|
|
|
}
|
2013-10-02 12:05:14 +02:00
|
|
|
}
|
2015-01-26 11:32:47 +01:00
|
|
|
if (format) {
|
2015-02-06 00:32:51 +01:00
|
|
|
return Sao.common.moment_format(format);
|
2015-01-26 11:32:47 +01:00
|
|
|
}
|
2015-02-06 00:32:51 +01:00
|
|
|
return '%Y-%m-%d';
|
2013-10-02 12:05:14 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
Sao.common.format_time = function(format, date) {
|
2015-03-13 13:39:21 +01:00
|
|
|
if (!date) {
|
|
|
|
return '';
|
|
|
|
}
|
2015-02-06 00:32:51 +01:00
|
|
|
return date.format(Sao.common.moment_format(format));
|
2013-10-02 12:05:14 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
Sao.common.parse_time = function(format, value) {
|
2013-10-04 11:21:32 +02:00
|
|
|
if (jQuery.isEmptyObject(value)) {
|
|
|
|
return null;
|
|
|
|
}
|
2013-10-02 12:05:14 +02:00
|
|
|
var getNumber = function(pattern) {
|
|
|
|
var i = format.indexOf(pattern);
|
|
|
|
if (~i) {
|
|
|
|
var number = parseInt(value.slice(i, i + pattern.length), 10);
|
|
|
|
if (!isNaN(number)) {
|
|
|
|
return number;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
};
|
2015-02-06 00:32:51 +01:00
|
|
|
return Sao.Time(getNumber('%H'), getNumber('%M'), getNumber('%S'),
|
2014-03-15 10:40:46 +01:00
|
|
|
getNumber('%f'));
|
2013-10-02 12:05:14 +02:00
|
|
|
};
|
|
|
|
|
2015-01-26 11:32:47 +01:00
|
|
|
Sao.common.format_date = function(date_format, date) {
|
|
|
|
if (!date) {
|
|
|
|
return '';
|
|
|
|
}
|
2015-02-06 00:32:51 +01:00
|
|
|
return date.format(Sao.common.moment_format(date_format));
|
2015-01-26 11:32:47 +01:00
|
|
|
};
|
|
|
|
|
2015-02-06 00:32:51 +01:00
|
|
|
Sao.common.parse_date = function(date_format, value) {
|
|
|
|
var date = moment(value,
|
|
|
|
Sao.common.moment_format(date_format));
|
|
|
|
if (date.isValid()) {
|
|
|
|
date = Sao.Date(date.year(), date.month(), date.date());
|
|
|
|
} else {
|
|
|
|
date = null;
|
2015-01-26 11:32:47 +01:00
|
|
|
}
|
2015-02-06 00:32:51 +01:00
|
|
|
return date;
|
2015-01-26 11:32:47 +01:00
|
|
|
};
|
|
|
|
|
2013-10-02 12:05:14 +02:00
|
|
|
Sao.common.format_datetime = function(date_format, time_format, date) {
|
2015-01-26 11:32:47 +01:00
|
|
|
if (!date) {
|
|
|
|
return '';
|
|
|
|
}
|
2015-02-06 00:32:51 +01:00
|
|
|
return date.format(
|
|
|
|
Sao.common.moment_format(date_format + ' ' + time_format));
|
2013-10-02 12:05:14 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
Sao.common.parse_datetime = function(date_format, time_format, value) {
|
2015-02-06 00:32:51 +01:00
|
|
|
var date = moment(value,
|
|
|
|
Sao.common.moment_format(date_format + ' ' + time_format));
|
|
|
|
if (date.isValid()) {
|
|
|
|
date = Sao.DateTime(date.year(), date.month(), date.date(),
|
|
|
|
date.hour(), date.minute(), date.second(),
|
|
|
|
date.millisecond());
|
|
|
|
} else {
|
|
|
|
date = null;
|
2014-07-09 00:36:28 +02:00
|
|
|
}
|
2013-10-02 12:05:14 +02:00
|
|
|
return date;
|
|
|
|
};
|
|
|
|
|
2015-02-09 23:04:01 +01:00
|
|
|
Sao.common.timedelta = {};
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER = {
|
|
|
|
's': 1
|
|
|
|
};
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.m =
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.s * 60;
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.h =
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.m * 60;
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.d =
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.h * 24;
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.w =
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.d * 7;
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.M =
|
2016-03-21 23:54:08 +01:00
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.d * 30;
|
2015-02-09 23:04:01 +01:00
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.Y =
|
|
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.d * 365;
|
|
|
|
Sao.common.timedelta._get_separator = function() {
|
|
|
|
return {
|
2015-05-29 10:44:10 +02:00
|
|
|
Y: Sao.i18n.gettext('Y'),
|
2015-05-29 13:42:18 +02:00
|
|
|
M: Sao.i18n.gettext('M'),
|
|
|
|
w: Sao.i18n.gettext('w'),
|
|
|
|
d: Sao.i18n.gettext('d'),
|
|
|
|
h: Sao.i18n.gettext('h'),
|
|
|
|
m: Sao.i18n.gettext('m'),
|
|
|
|
s: Sao.i18n.gettext('s')
|
2015-02-09 23:04:01 +01:00
|
|
|
};
|
|
|
|
};
|
|
|
|
Sao.common.timedelta.format = function(value, converter) {
|
|
|
|
if (!value) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
if (!converter) {
|
|
|
|
converter = Sao.common.timedelta.DEFAULT_CONVERTER;
|
|
|
|
}
|
|
|
|
var text = [];
|
|
|
|
value = value.asSeconds();
|
|
|
|
var sign = '';
|
|
|
|
if (value < 0) {
|
|
|
|
sign = '-';
|
|
|
|
}
|
|
|
|
value = Math.abs(value);
|
|
|
|
converter = Object.keys(converter).map(function(key) {
|
|
|
|
return [key, converter[key]];
|
|
|
|
});
|
|
|
|
converter.sort(function(first, second) {
|
|
|
|
return second[1] - first[1];
|
|
|
|
});
|
|
|
|
var values = [];
|
|
|
|
var k, v;
|
|
|
|
for (var i = 0; i < converter.length; i++) {
|
|
|
|
k = converter[i][0];
|
|
|
|
v = converter[i][1];
|
|
|
|
var part = Math.floor(value / v);
|
|
|
|
value -= part * v;
|
|
|
|
values.push(part);
|
|
|
|
}
|
|
|
|
for (i = 0; i < converter.length - 3; i++) {
|
|
|
|
k = converter[i][0];
|
|
|
|
v = values[i];
|
|
|
|
if (v) {
|
|
|
|
text.push(v + Sao.common.timedelta._get_separator()[k]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (jQuery(values.slice(-3)).is(function(i, v) { return v; }) ||
|
|
|
|
jQuery.isEmptyObject(text)) {
|
|
|
|
var time = values.slice(-3, -1);
|
|
|
|
time = ('00' + time[0]).slice(-2) + ':' + ('00' + time[1]).slice(-2);
|
|
|
|
if (values.slice(-1)[0] || value) {
|
|
|
|
time += ':' + ('00' + values.slice(-1)[0]).slice(-2);
|
|
|
|
}
|
|
|
|
text.push(time);
|
|
|
|
}
|
|
|
|
text = sign + text.reduce(function(p, c) {
|
|
|
|
if (p) {
|
|
|
|
return p + ' ' + c;
|
|
|
|
} else {
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (value) {
|
|
|
|
if (!jQuery(values.slice(-3)).is(function(i, v) { return v; })) {
|
|
|
|
// Add space if no time
|
|
|
|
text += ' ';
|
|
|
|
}
|
|
|
|
text += ('' + value.toFixed(6)).slice(1);
|
|
|
|
}
|
2013-01-22 17:13:26 +01:00
|
|
|
return text;
|
|
|
|
};
|
2015-02-09 23:04:01 +01:00
|
|
|
Sao.common.timedelta.parse = function(text, converter) {
|
|
|
|
if (!text) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (!converter) {
|
|
|
|
converter = Sao.common.timedelta.DEFAULT_CONVERTER;
|
|
|
|
}
|
|
|
|
var separators = Sao.common.timedelta._get_separator();
|
|
|
|
var separator;
|
|
|
|
for (var k in separators) {
|
|
|
|
separator = separators[k];
|
|
|
|
text = text.replace(separator, separator + ' ');
|
|
|
|
}
|
|
|
|
|
|
|
|
var seconds = 0;
|
|
|
|
var sec;
|
|
|
|
var parts = text.split(' ');
|
|
|
|
for (var i = 0; i < parts.length; i++) {
|
|
|
|
var part = parts[i];
|
|
|
|
if (part.contains(':')) {
|
|
|
|
var subparts = part.split(':');
|
|
|
|
var subconverter = [
|
|
|
|
converter.h, converter.m, converter.s];
|
|
|
|
for (var j = 0;
|
|
|
|
j < Math.min(subparts.length, subconverter.length);
|
|
|
|
j ++) {
|
|
|
|
var t = subparts[j];
|
|
|
|
var v = subconverter[j];
|
|
|
|
sec = Math.abs(Number(t)) * v;
|
|
|
|
if (!isNaN(sec)) {
|
|
|
|
seconds += sec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var found = false;
|
|
|
|
for (var key in separators) {
|
|
|
|
separator =separators[key];
|
|
|
|
if (part.endsWith(separator)) {
|
|
|
|
part = part.slice(0, -separator.length);
|
|
|
|
sec = Math.abs(parseInt(part, 10)) * converter[key];
|
|
|
|
if (!isNaN(sec)) {
|
|
|
|
seconds += sec;
|
|
|
|
}
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
sec = Math.abs(Number(part));
|
|
|
|
if (!isNaN(sec)) {
|
|
|
|
seconds += sec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (text.contains('-')) {
|
|
|
|
seconds *= -1;
|
|
|
|
}
|
|
|
|
return Sao.TimeDelta(null, seconds);
|
|
|
|
};
|
2013-01-23 19:37:27 +01:00
|
|
|
|
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();
|
|
|
|
|
2014-03-18 19:56:07 +01:00
|
|
|
Sao.common.ModelHistory = Sao.class_(Object, {
|
|
|
|
init: function() {
|
|
|
|
this._models = [];
|
|
|
|
},
|
|
|
|
load_history: function() {
|
|
|
|
this._models = [];
|
|
|
|
return Sao.rpc({
|
|
|
|
'method': 'model.ir.model.list_history',
|
|
|
|
'params': [{}]
|
|
|
|
}, Sao.Session.current_session).then(function(models) {
|
|
|
|
this._models = models;
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
contains: function(model) {
|
|
|
|
return ~this._models.indexOf(model);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.MODELHISTORY = new Sao.common.ModelHistory();
|
|
|
|
|
2015-03-06 09:33:35 +01:00
|
|
|
Sao.common.ViewSearch = Sao.class_(Object, {
|
|
|
|
load_searches: function() {
|
|
|
|
this.searches = {};
|
|
|
|
return Sao.rpc({
|
|
|
|
'method': 'model.ir.ui.view_search.get_search',
|
|
|
|
'params': [{}]
|
|
|
|
}, Sao.Session.current_session).then(function(searches) {
|
|
|
|
this.searches = searches;
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
get: function(model) {
|
|
|
|
return this.searches[model] || [];
|
|
|
|
},
|
|
|
|
add: function(model, name, domain) {
|
|
|
|
return Sao.rpc({
|
|
|
|
'method': 'model.ir.ui.view_search.create',
|
|
|
|
'params': [[{
|
|
|
|
'model': model,
|
|
|
|
'name': name,
|
|
|
|
'domain': new Sao.PYSON.Encoder().encode(domain)
|
|
|
|
}], {}]
|
|
|
|
}, Sao.Session.current_session).then(function(ids) {
|
|
|
|
var id = ids[0];
|
|
|
|
if (this.searches[model] === undefined) {
|
|
|
|
this.searches[model] = [];
|
|
|
|
}
|
|
|
|
this.searches[model].push([id, name, domain]);
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
remove: function(model, id) {
|
|
|
|
return Sao.rpc({
|
|
|
|
'method': 'model.ir.ui.view_search.delete',
|
|
|
|
'params': [[id], {}]
|
|
|
|
}, Sao.Session.current_session).then(function() {
|
|
|
|
for (var i = 0; i < this.searches[model].length; i++) {
|
|
|
|
var domain = this.searches[model][i];
|
|
|
|
if (domain[0] === id) {
|
|
|
|
this.searches[model].splice(i, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}.bind(this));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.VIEW_SEARCH = new Sao.common.ViewSearch();
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-01-23 19:37:27 +01:00
|
|
|
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];
|
2013-01-23 19:37:27 +01:00
|
|
|
environment[key] = field.get_on_change_value(parent_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
environment.id = parent_.id;
|
2013-07-01 11:58:10 +02:00
|
|
|
if (parent_.group.parent)
|
2013-02-04 18:29:15 +01:00
|
|
|
Object.defineProperty(environment, '_parent_' +
|
2013-07-01 11:58:10 +02:00
|
|
|
parent_.group.parent_name, {
|
2013-01-23 19:37:27 +01:00
|
|
|
'enumerable': true,
|
2013-02-04 18:29:15 +01:00
|
|
|
'get': function() {
|
2013-07-01 11:58:10 +02:00
|
|
|
return Sao.common.EvalEnvironment(parent_.group.parent,
|
2013-02-04 18:29:15 +01:00
|
|
|
eval_type);
|
2013-01-23 19:37:27 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
environment.get = function(item, default_) {
|
|
|
|
if (this.hasOwnProperty(item))
|
|
|
|
return this[item];
|
|
|
|
return default_;
|
|
|
|
};
|
|
|
|
|
|
|
|
return environment;
|
|
|
|
};
|
2013-02-06 17:34:32 +01:00
|
|
|
|
|
|
|
Sao.common.selection_mixin = {};
|
|
|
|
Sao.common.selection_mixin.init = function() {
|
|
|
|
this.selection = null;
|
2013-10-01 11:48:56 +02:00
|
|
|
this.inactive_selection = [];
|
2013-02-06 17:34:32 +01:00
|
|
|
this._last_domain = null;
|
|
|
|
this._values2selection = {};
|
|
|
|
this._domain_cache = {};
|
2014-05-23 11:52:21 +02:00
|
|
|
if (this.nullable_widget === undefined) {
|
|
|
|
this.nullable_widget = true;
|
|
|
|
}
|
2013-02-06 17:34:32 +01:00
|
|
|
};
|
2014-06-22 16:51:42 +02:00
|
|
|
Sao.common.selection_mixin.init_selection = function(value, callback) {
|
|
|
|
if (!value) {
|
|
|
|
value = {};
|
2013-02-06 17:34:32 +01:00
|
|
|
(this.attributes.selection_change_with || []).forEach(function(e) {
|
2014-06-22 16:51:42 +02:00
|
|
|
value[e] = null;
|
2013-02-06 17:34:32 +01:00
|
|
|
});
|
|
|
|
}
|
2014-06-22 16:51:42 +02:00
|
|
|
var key = JSON.stringify(value);
|
2013-09-05 19:24:47 +02:00
|
|
|
var selection = this.attributes.selection || [];
|
2015-03-06 16:26:47 +01:00
|
|
|
var prm;
|
2013-02-06 17:34:32 +01:00
|
|
|
var prepare_selection = function(selection) {
|
2013-09-05 19:24:47 +02:00
|
|
|
selection = jQuery.extend([], selection);
|
2013-02-06 17:34:32 +01:00
|
|
|
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);
|
|
|
|
};
|
2013-09-05 19:24:47 +02:00
|
|
|
if (!(selection instanceof Array) &&
|
2013-02-06 17:34:32 +01:00
|
|
|
!(key in this._values2selection)) {
|
2015-03-26 19:37:30 +01:00
|
|
|
if (!jQuery.isEmptyObject(this.attributes.selection_change_with)) {
|
2014-06-22 16:51:42 +02:00
|
|
|
prm = this.model.execute(selection, [value]);
|
2013-02-06 17:34:32 +01:00
|
|
|
} else {
|
2013-09-05 19:24:47 +02:00
|
|
|
prm = this.model.execute(selection, []);
|
2013-02-06 17:34:32 +01:00
|
|
|
}
|
2015-10-18 16:28:40 +02:00
|
|
|
prm = prm.then(function(selection) {
|
|
|
|
this._values2selection[key] = selection;
|
|
|
|
return selection;
|
|
|
|
}.bind(this));
|
2015-03-06 16:26:47 +01:00
|
|
|
prm = prm.then(prepare_selection.bind(this));
|
2013-02-06 17:34:32 +01:00
|
|
|
} else {
|
|
|
|
if (key in this._values2selection) {
|
2015-10-18 16:28:40 +02:00
|
|
|
selection = this._values2selection[key];
|
2013-02-06 17:34:32 +01:00
|
|
|
}
|
|
|
|
prepare_selection.call(this, selection);
|
2015-03-06 16:26:47 +01:00
|
|
|
prm = jQuery.when();
|
2013-02-06 17:34:32 +01:00
|
|
|
}
|
2013-10-01 11:48:56 +02:00
|
|
|
this.inactive_selection = [];
|
2015-03-06 16:26:47 +01:00
|
|
|
this._selection_prm = prm;
|
2013-02-06 17:34:32 +01:00
|
|
|
};
|
|
|
|
Sao.common.selection_mixin.update_selection = function(record, field,
|
|
|
|
callback) {
|
2015-03-06 17:12:18 +01:00
|
|
|
var _update_selection = function() {
|
2015-03-06 16:26:47 +01:00
|
|
|
if (!field) {
|
2013-09-06 21:13:13 +02:00
|
|
|
if (callback) {
|
|
|
|
callback(this.selection);
|
|
|
|
}
|
2013-02-06 17:34:32 +01:00
|
|
|
return;
|
|
|
|
}
|
2015-03-06 16:26:47 +01:00
|
|
|
var domain = field.get_domain(record);
|
|
|
|
if (field.description.type == 'reference') {
|
|
|
|
// The domain on reference field is not only based on the
|
|
|
|
// selection so the selection can not be filtered.
|
|
|
|
domain = [];
|
|
|
|
}
|
|
|
|
if (!('relation' in this.attributes)) {
|
|
|
|
var change_with = this.attributes.selection_change_with || [];
|
|
|
|
var value = record._get_on_change_args(change_with);
|
|
|
|
delete value.id;
|
|
|
|
Sao.common.selection_mixin.init_selection.call(this, value,
|
|
|
|
function() {
|
|
|
|
Sao.common.selection_mixin.filter_selection.call(
|
|
|
|
this, domain, record, field);
|
|
|
|
if (callback) {
|
|
|
|
callback(this.selection);
|
|
|
|
}
|
|
|
|
}.bind(this));
|
|
|
|
} else {
|
|
|
|
var context = field.get_context(record);
|
|
|
|
var jdomain = JSON.stringify([domain, context]);
|
|
|
|
if (jdomain in this._domain_cache) {
|
|
|
|
this.selection = this._domain_cache[jdomain];
|
|
|
|
this._last_domain = [domain, context];
|
|
|
|
}
|
|
|
|
if ((this._last_domain !== null) &&
|
|
|
|
Sao.common.compare(domain, this._last_domain[0]) &&
|
|
|
|
(JSON.stringify(context) ==
|
|
|
|
JSON.stringify(this._last_domain[1]))) {
|
|
|
|
if (callback) {
|
|
|
|
callback(this.selection);
|
|
|
|
}
|
|
|
|
return;
|
2013-02-06 17:34:32 +01:00
|
|
|
}
|
2015-03-06 16:26:47 +01:00
|
|
|
var prm = Sao.rpc({
|
|
|
|
'method': 'model.' + this.attributes.relation +
|
|
|
|
'.search_read',
|
|
|
|
'params': [domain, 0, null, null, ['rec_name'], context]
|
|
|
|
}, record.model.session);
|
|
|
|
prm.done(function(result) {
|
|
|
|
var selection = [];
|
|
|
|
result.forEach(function(x) {
|
|
|
|
selection.push([x.id, x.rec_name]);
|
|
|
|
});
|
|
|
|
if (this.nullable_widget) {
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this._selection_prm.done(_update_selection.bind(this));
|
2013-02-06 17:34:32 +01:00
|
|
|
};
|
2014-03-15 10:47:02 +01:00
|
|
|
Sao.common.selection_mixin.filter_selection = function(
|
|
|
|
domain, record, field) {
|
|
|
|
if (jQuery.isEmptyObject(domain)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var inversion = new Sao.common.DomainInversion();
|
|
|
|
this.selection = this.selection.filter(function(value) {
|
|
|
|
var context = {};
|
|
|
|
context[this.field_name] = value[0];
|
|
|
|
return inversion.eval_domain(domain, context);
|
|
|
|
}.bind(this));
|
|
|
|
};
|
2013-10-01 11:48:56 +02:00
|
|
|
Sao.common.selection_mixin.get_inactive_selection = function(value) {
|
|
|
|
if (!this.attributes.relation) {
|
|
|
|
return jQuery.when([]);
|
|
|
|
}
|
|
|
|
for (var i = 0, len = this.inactive_selection.length; i < len; i++) {
|
|
|
|
if (value == this.inactive_selection[i][0]) {
|
|
|
|
return jQuery.when(this.inactive_selection[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var prm = Sao.rpc({
|
|
|
|
'method': 'model.' + this.attributes.relation + '.read',
|
2013-10-01 13:56:57 +02:00
|
|
|
'params': [[value], ['rec_name'], {}]
|
2013-10-01 11:48:56 +02:00
|
|
|
}, Sao.Session.current_session);
|
|
|
|
return prm.then(function(result) {
|
|
|
|
this.inactive_selection.push([result[0].id, result[0].rec_name]);
|
|
|
|
return [result[0].id, result[0].rec_name];
|
|
|
|
}.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;
|
2015-02-06 00:32:51 +01:00
|
|
|
this.el = jQuery('<button/>', {
|
2015-02-27 10:25:02 +01:00
|
|
|
'class': 'btn btn-default',
|
|
|
|
'type': 'button'
|
2015-10-04 19:41:59 +02:00
|
|
|
});
|
|
|
|
this.icon = jQuery('<img/>', {
|
|
|
|
'class': 'icon',
|
2015-02-06 00:32:51 +01:00
|
|
|
'aria-hidden': true
|
2015-10-04 19:41:59 +02:00
|
|
|
}).appendTo(this.el);
|
2015-10-11 13:55:05 +02:00
|
|
|
this.icon.hide();
|
2015-10-04 19:41:59 +02:00
|
|
|
this.el.append(attributes.string || '');
|
2013-08-28 18:07:43 +02:00
|
|
|
this.set_icon(attributes.icon);
|
|
|
|
},
|
|
|
|
set_icon: function(icon_name) {
|
|
|
|
if (!icon_name) {
|
2015-10-04 19:41:59 +02:00
|
|
|
this.icon.attr('src', '');
|
2015-10-11 13:55:05 +02:00
|
|
|
this.icon.hide();
|
2013-08-28 18:07:43 +02:00
|
|
|
return;
|
|
|
|
}
|
2013-09-04 11:07:41 +02:00
|
|
|
var prm = Sao.common.ICONFACTORY.register_icon(icon_name);
|
2013-08-28 18:07:43 +02:00
|
|
|
prm.done(function(url) {
|
2015-10-04 19:41:59 +02:00
|
|
|
this.icon.attr('src', url);
|
2015-10-11 13:55:05 +02:00
|
|
|
this.icon.show();
|
2013-08-28 18:07:43 +02:00
|
|
|
}.bind(this));
|
2013-05-02 17:23:37 +02:00
|
|
|
},
|
2013-05-06 15:03:25 +02:00
|
|
|
set_state: function(record) {
|
|
|
|
var states;
|
|
|
|
if (record) {
|
|
|
|
states = record.expr_eval(this.attributes.states || {});
|
2015-04-30 13:57:29 +02:00
|
|
|
if (record.group.get_readonly() || record.readonly()) {
|
2014-05-17 18:33:25 +02:00
|
|
|
states.readonly = true;
|
|
|
|
}
|
2013-05-06 15:03:25 +02:00
|
|
|
} else {
|
|
|
|
states = {};
|
|
|
|
}
|
|
|
|
if (states.invisible) {
|
|
|
|
this.el.hide();
|
|
|
|
} else {
|
|
|
|
this.el.show();
|
|
|
|
}
|
|
|
|
this.el.prop('disabled', states.readonly);
|
2013-08-28 18:07:43 +02:00
|
|
|
this.set_icon(states.icon || this.attributes.icon);
|
2015-03-16 12:58:22 +01:00
|
|
|
if (((this.attributes.type === undefined) ||
|
|
|
|
(this.attributes.type === 'class')) && (record)) {
|
2013-05-06 15:03:25 +02:00
|
|
|
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;
|
2013-08-08 19:43:30 +02:00
|
|
|
} 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;
|
2013-08-08 19:43:30 +02:00
|
|
|
} 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: ['!=', '<=', '>=', '=', '!', '<', '>'],
|
2015-07-09 15:32:45 +02:00
|
|
|
init: function(fields, context) {
|
2013-05-16 18:50:15 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2015-07-09 15:32:45 +02:00
|
|
|
this.context = context;
|
2013-05-16 18:50:15 +02:00
|
|
|
},
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
},
|
2015-03-06 09:33:35 +01:00
|
|
|
stringable: function(domain) {
|
|
|
|
var stringable_ = function(clause) {
|
|
|
|
if (!clause) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
var is_array = function(e) {
|
|
|
|
return e instanceof Array;
|
|
|
|
};
|
|
|
|
if ((~['AND', 'OR'].indexOf(clause[0]) ||
|
|
|
|
(is_array(clause[0]))) &&
|
|
|
|
clause.slice(1).every(is_array)) {
|
|
|
|
return this.stringable(clause);
|
|
|
|
}
|
|
|
|
if ((clause[0] in this.fields) || clause[0] == 'rec_name') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}.bind(this);
|
|
|
|
if (!domain) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (~['AND', 'OR'].indexOf(domain[0])) {
|
|
|
|
domain = domain.slice(1);
|
|
|
|
}
|
|
|
|
return domain.every(stringable_);
|
|
|
|
},
|
2013-05-16 18:50:15 +02:00
|
|
|
string: function(domain) {
|
|
|
|
|
|
|
|
var string = function(clause) {
|
|
|
|
if (jQuery.isEmptyObject(clause)) {
|
|
|
|
return '';
|
|
|
|
}
|
2013-11-16 18:44:33 +01:00
|
|
|
if ((typeof clause[0] != 'string') ||
|
|
|
|
~['AND', 'OR'].indexOf(clause[0])) {
|
|
|
|
return '(' + this.string(clause) + ')';
|
|
|
|
}
|
2013-05-16 18:50:15 +02:00
|
|
|
var escaped;
|
2013-11-16 18:44:33 +01:00
|
|
|
var name = clause[0];
|
|
|
|
var operator = clause[1];
|
|
|
|
var value = clause[2];
|
|
|
|
if (name.endsWith('.rec_name')) {
|
|
|
|
name = name.slice(0, -9);
|
|
|
|
}
|
|
|
|
if (!(name in this.fields)) {
|
|
|
|
escaped = value.replace('%%', '__');
|
|
|
|
if (escaped.startsWith('%') && escaped.endsWith('%')) {
|
|
|
|
value = value.slice(1, -1);
|
2013-05-16 18:50:15 +02:00
|
|
|
}
|
2013-11-16 18:44:33 +01:00
|
|
|
return this.quote(value);
|
|
|
|
}
|
|
|
|
var field = this.fields[name];
|
|
|
|
var target = null;
|
|
|
|
if (clause.length > 3) {
|
|
|
|
target = clause[3];
|
|
|
|
}
|
|
|
|
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 = '=';
|
2013-05-16 18:50:15 +02:00
|
|
|
} else {
|
2013-11-16 18:44:33 +01:00
|
|
|
operator = '!';
|
2013-05-16 18:50:15 +02:00
|
|
|
}
|
2013-11-16 18:44:33 +01:00
|
|
|
value = value.replace('%%', '%');
|
2013-05-16 18:50:15 +02:00
|
|
|
}
|
2013-11-16 18:44:33 +01:00
|
|
|
}
|
|
|
|
var def_operator = this.default_operator(field);
|
2016-02-02 19:09:55 +01:00
|
|
|
if (def_operator == operator.trim()) {
|
|
|
|
operator = '';
|
|
|
|
if (~this.OPERATORS.indexOf(value)) {
|
|
|
|
// As the value could be interpreted as an operator
|
|
|
|
// the default operator must be forced
|
|
|
|
operator = '"" ';
|
|
|
|
}
|
|
|
|
} else if ((operator.contains(def_operator) &&
|
|
|
|
(operator.contains('not') ||
|
|
|
|
operator.contains('!')))) {
|
2013-11-16 18:44:33 +01:00
|
|
|
operator = operator.replace(def_operator, '')
|
|
|
|
.replace('not', '!').trim();
|
|
|
|
}
|
|
|
|
if (operator.endsWith('in')) {
|
|
|
|
if (operator == 'not in') {
|
|
|
|
operator = '!';
|
|
|
|
} else {
|
|
|
|
operator = '';
|
2013-05-16 18:50:15 +02:00
|
|
|
}
|
|
|
|
}
|
2013-11-16 18:44:33 +01:00
|
|
|
var formatted_value = this.format_value(field, value, target);
|
|
|
|
if (~this.OPERATORS.indexOf(operator) &&
|
2014-03-07 19:39:49 +01:00
|
|
|
~['char', 'text', 'selection']
|
2013-11-16 18:44:33 +01:00
|
|
|
.indexOf(field.type) &&
|
|
|
|
(value === '')) {
|
|
|
|
formatted_value = '""';
|
|
|
|
}
|
|
|
|
return (this.quote(field.string) + ': ' +
|
|
|
|
operator + formatted_value);
|
2013-05-16 18:50:15 +02:00
|
|
|
};
|
|
|
|
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);
|
|
|
|
},
|
2015-04-24 13:56:41 +02:00
|
|
|
completion: function(input) {
|
|
|
|
var results = [];
|
|
|
|
var domain = this.parse(input);
|
|
|
|
var closing = 0;
|
|
|
|
var i, len;
|
|
|
|
for (i=input.length; i>0; i--) {
|
|
|
|
if (input[i] == ')' || input[i] == ' ') {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (input[i] == ')') {
|
|
|
|
closing += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var endings = this.ending_clause(domain);
|
|
|
|
var ending = endings[0];
|
|
|
|
var deep_ending = endings[1];
|
|
|
|
var deep = deep_ending - closing;
|
|
|
|
var string_domain = this.string(domain);
|
|
|
|
|
|
|
|
if (deep > 0) {
|
|
|
|
string_domain = string_domain.substring(0,
|
|
|
|
string_domain.length - deep);
|
|
|
|
}
|
|
|
|
if (string_domain != input) {
|
|
|
|
results.push(string_domain);
|
|
|
|
}
|
|
|
|
|
|
|
|
var pslice = function(string, depth) {
|
|
|
|
if (depth > 0) {
|
|
|
|
return string.substring(0, depth);
|
|
|
|
}
|
|
|
|
return string;
|
|
|
|
};
|
|
|
|
var complete, complete_string;
|
|
|
|
if (ending !== null && closing === 0) {
|
|
|
|
var completes = this.complete(ending);
|
|
|
|
for (i=0, len=completes.length; i < len; i++) {
|
|
|
|
complete = completes[i];
|
|
|
|
complete_string = this.string(
|
|
|
|
this.replace_ending_clause(domain, complete));
|
|
|
|
results.push(pslice(complete_string, deep));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (input.length > 0) {
|
|
|
|
if (input.substr(input.length - 1, 1) != ' ') {
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
if (input.length >= 2 ||
|
|
|
|
input.substr(input.length - 2, 1) == ':') {
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var field, operator, value;
|
|
|
|
for (var key in this.strings) {
|
|
|
|
field = this.strings[key];
|
|
|
|
operator = this.default_operator(field);
|
|
|
|
value = '';
|
|
|
|
if ((operator == 'ilike') || (operator == 'not ilike')) {
|
|
|
|
value = this.likify(value);
|
|
|
|
}
|
2016-03-15 17:46:26 +01:00
|
|
|
var new_domain = this.append_ending_clause(domain,
|
2015-04-24 13:56:41 +02:00
|
|
|
[field.name, operator, value], deep);
|
|
|
|
new_domain_string = this.string(new_domain);
|
|
|
|
results.push(pslice(new_domain_string, deep));
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
},
|
|
|
|
complete: function(clause) {
|
|
|
|
var results = [];
|
|
|
|
var name, operator, value, target;
|
|
|
|
if (clause.length == 1) {
|
|
|
|
name = clause[0];
|
|
|
|
} else if (clause.length == 3) {
|
|
|
|
name = clause[0];
|
|
|
|
operator = clause[1];
|
|
|
|
value = clause[2];
|
|
|
|
} else {
|
|
|
|
name = clause[0];
|
|
|
|
operator = clause[1];
|
|
|
|
value = clause[2];
|
|
|
|
target = clause[3];
|
|
|
|
if (name.endsWith('.rec_name')) {
|
|
|
|
name = name.substring(0, name.length - 9);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var escaped;
|
|
|
|
if (name == "rec_name") {
|
|
|
|
if (operator == "ilike") {
|
|
|
|
escaped = value.replace(/%%/g, '__');
|
|
|
|
if (escaped.startsWith('%') || escaped.endsWith('%')) {
|
|
|
|
value = escaped.substring(1, escaped.length - 1);
|
|
|
|
} else if (~escaped.indexOf('%')) {
|
|
|
|
value = value.replace(/%%/g, '%');
|
|
|
|
}
|
|
|
|
operator = null;
|
|
|
|
}
|
|
|
|
name = value;
|
|
|
|
value = '';
|
|
|
|
}
|
|
|
|
var field;
|
|
|
|
if (!(name.toLowerCase() in this.strings) &&
|
|
|
|
!(name in this.fields)) {
|
|
|
|
for (var key in this.strings) {
|
|
|
|
field = this.strings[key];
|
|
|
|
if (field.string.toLowerCase()
|
|
|
|
.startsWith(name.toLowerCase())) {
|
|
|
|
operator = this.default_operator(field);
|
|
|
|
value = '';
|
|
|
|
if (operator == 'ilike') {
|
|
|
|
value = this.likify(value);
|
|
|
|
}
|
|
|
|
results.push([field.name, operator, value]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
if (name in this.fields) {
|
|
|
|
field = this.fields[name];
|
|
|
|
} else {
|
|
|
|
field = this.strings[name.toLowerCase()];
|
|
|
|
}
|
2016-02-02 19:09:55 +01:00
|
|
|
if (!operator) {
|
2015-04-24 13:56:41 +02:00
|
|
|
operator = this.default_operator(field);
|
|
|
|
value = '';
|
|
|
|
if ((operator == 'ilike') || (operator == 'not ilike')) {
|
|
|
|
value = this.likify(value);
|
|
|
|
}
|
|
|
|
results.push([field.name, operator, value]);
|
|
|
|
} else {
|
|
|
|
var completes = this.complete_value(field, value);
|
|
|
|
for (var i=0, len=completes.length; i < len; i++) {
|
|
|
|
results.push([field.name, operator, completes[i]]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
},
|
|
|
|
is_leaf: function(element) {
|
|
|
|
return ((element instanceof Array) && element.clause);
|
|
|
|
},
|
|
|
|
ending_clause: function(domain, depth) {
|
|
|
|
if (depth === undefined) {
|
|
|
|
depth = 0;
|
|
|
|
}
|
|
|
|
if (domain.length === 0) {
|
|
|
|
return [null, depth];
|
|
|
|
}
|
|
|
|
var last_element = domain[domain.length - 1];
|
|
|
|
if (!this.is_leaf(last_element)) {
|
|
|
|
return this.ending_clause(last_element, depth + 1);
|
|
|
|
}
|
|
|
|
return [last_element, depth];
|
|
|
|
},
|
|
|
|
replace_ending_clause: function(domain, clause) {
|
|
|
|
var results = [];
|
|
|
|
var i, len;
|
|
|
|
for (i = 0, len=domain.length - 1; i < len; i++) {
|
|
|
|
results.push(domain[i]);
|
|
|
|
}
|
|
|
|
if (!this.is_leaf(domain[i])) {
|
|
|
|
results = results.concat(this.replace_ending_clause(domain[i],
|
|
|
|
clause));
|
|
|
|
} else {
|
|
|
|
results.push(clause);
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
},
|
|
|
|
append_ending_clause: function(domain, clause, depth) {
|
|
|
|
if (domain.length === 0) {
|
|
|
|
return [clause];
|
|
|
|
}
|
|
|
|
var results = domain.slice(0, -1);
|
|
|
|
var last_element = domain[domain.length - 1];
|
|
|
|
if (!this.is_leaf(last_element)) {
|
|
|
|
results.push(this.append_ending_clause(last_element, clause,
|
|
|
|
depth - 1));
|
|
|
|
} else {
|
|
|
|
results.push(last_element);
|
|
|
|
if (depth === 0) {
|
|
|
|
results.push(clause);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
},
|
|
|
|
complete_value: function(field, value) {
|
|
|
|
var complete_boolean = function() {
|
|
|
|
return value ? [true] : [false];
|
|
|
|
};
|
|
|
|
|
|
|
|
var complete_selection = function() {
|
|
|
|
var results = [];
|
|
|
|
var test_value = value !== null ? value : '';
|
|
|
|
if (value instanceof Array) {
|
|
|
|
test_value = value[value.length - 1];
|
|
|
|
}
|
|
|
|
test_value = test_value.replace(/^%*|%*$/g, '');
|
|
|
|
var i, len, svalue, test;
|
|
|
|
for (i=0, len=field.selection.length; i<len; i++) {
|
|
|
|
svalue = field.selection[i][0];
|
|
|
|
test = field.selection[i][1].toLowerCase();
|
|
|
|
if (test.startsWith(test_value.toLowerCase())) {
|
|
|
|
if (value instanceof Array) {
|
|
|
|
results.push(value.slice(0, -1).concat([svalue]));
|
|
|
|
} else {
|
|
|
|
results.push(svalue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
};
|
|
|
|
|
|
|
|
var complete_reference = function() {
|
|
|
|
var results = [];
|
|
|
|
var test_value = value !== null ? value : '';
|
|
|
|
if (value instanceof Array) {
|
|
|
|
test_value = value[value.length - 1];
|
|
|
|
}
|
|
|
|
test_value = test_value.replace(/^%*|%*$/g, '');
|
|
|
|
var i, len, svalue, test;
|
|
|
|
for (i=0, len=field.selection.length; i<len; i++) {
|
|
|
|
svalue = field.selection[i][0];
|
|
|
|
test = field.selection[i][1].toLowerCase();
|
|
|
|
if (test.startsWith(test_value.toLowerCase())) {
|
|
|
|
if (value instanceof Array) {
|
|
|
|
results.push(value.slice(0, -1).concat([svalue]));
|
|
|
|
} else {
|
|
|
|
results.push(this.likify(svalue));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
};
|
|
|
|
|
|
|
|
var complete_datetime = function() {
|
|
|
|
return [Sao.Date(), Sao.DateTime().utc()];
|
|
|
|
};
|
|
|
|
|
|
|
|
var complete_date = function() {
|
|
|
|
return [Sao.Date()];
|
|
|
|
};
|
|
|
|
|
|
|
|
var complete_time = function() {
|
|
|
|
return [Sao.Time()];
|
|
|
|
};
|
|
|
|
|
|
|
|
var completes = {
|
|
|
|
'boolean': complete_boolean,
|
|
|
|
'selection': complete_selection,
|
|
|
|
'reference': complete_reference,
|
|
|
|
'datetime': complete_datetime,
|
|
|
|
'date': complete_date,
|
|
|
|
'time': complete_time
|
|
|
|
};
|
|
|
|
|
|
|
|
if (field.type in completes) {
|
|
|
|
return completes[field.type]();
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
},
|
2013-05-16 18:50:15 +02:00
|
|
|
group_operator: function(tokens) {
|
|
|
|
var cur = tokens[0];
|
|
|
|
var nex = null;
|
|
|
|
var result = [];
|
|
|
|
tokens.slice(1).forEach(function(nex) {
|
|
|
|
if ((nex == '=') && cur &&
|
2013-08-08 19:43:30 +02:00
|
|
|
~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(':');
|
2013-08-08 19:43:30 +02:00
|
|
|
if (!~i) {
|
2013-05-16 18:50:15 +02:00
|
|
|
parts.forEach(push_result);
|
2015-04-24 13:56:41 +02:00
|
|
|
result.forEach(function (e) {
|
|
|
|
e.clause = true;
|
|
|
|
});
|
2013-05-16 18:50:15 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
var sub_group = function(name, lvalue) {
|
|
|
|
return function(part) {
|
|
|
|
if (!jQuery.isEmptyObject(name)) {
|
2015-03-25 18:41:43 +01:00
|
|
|
var clause;
|
2013-05-16 18:50:15 +02:00
|
|
|
if (!jQuery.isEmptyObject(lvalue)) {
|
|
|
|
if (part[0] !== null) {
|
|
|
|
lvalue.push(part[0]);
|
|
|
|
}
|
2015-03-25 18:41:43 +01:00
|
|
|
clause = name.concat([lvalue]);
|
|
|
|
clause.clause = true;
|
|
|
|
result.push(clause);
|
2013-05-16 18:50:15 +02:00
|
|
|
} else {
|
2015-03-25 18:41:43 +01:00
|
|
|
clause = name.concat(part);
|
|
|
|
clause.clause = true;
|
|
|
|
result.push(clause);
|
2013-05-16 18:50:15 +02:00
|
|
|
}
|
|
|
|
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];
|
2016-02-02 19:09:55 +01:00
|
|
|
// empty string is also the default operator
|
|
|
|
var operators = [''].concat(this.OPERATORS);
|
2013-05-16 18:50:15 +02:00
|
|
|
if (((i + 1) < parts.length) &&
|
2016-02-02 19:09:55 +01:00
|
|
|
(~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)) {
|
2015-03-25 18:41:43 +01:00
|
|
|
var clause;
|
2013-05-16 18:50:15 +02:00
|
|
|
if (!jQuery.isEmptyObject(lvalue)) {
|
2015-03-25 18:41:43 +01:00
|
|
|
clause = name.concat([lvalue]);
|
|
|
|
clause.clause = true;
|
|
|
|
result.push(clause);
|
2013-05-16 18:50:15 +02:00
|
|
|
} else {
|
2015-03-25 18:41:43 +01:00
|
|
|
clause = name.concat([null]);
|
|
|
|
clause.clause = true;
|
|
|
|
result.push(clause);
|
2013-05-16 18:50:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
_group = _group.bind(this);
|
|
|
|
|
|
|
|
var parts = [];
|
|
|
|
tokens.forEach(function(token) {
|
2015-03-25 18:41:43 +01:00
|
|
|
if (this.is_generator(token)) {
|
2013-05-16 18:50:15 +02:00
|
|
|
_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;
|
|
|
|
},
|
2015-03-25 18:41:43 +01:00
|
|
|
is_generator: function(value) {
|
|
|
|
return (value instanceof Array) && (value.clause === undefined);
|
|
|
|
},
|
2013-05-16 18:50:15 +02:00
|
|
|
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;
|
|
|
|
}
|
2015-03-25 18:41:43 +01:00
|
|
|
if (this.is_generator(cur)) {
|
2013-05-16 18:50:15 +02:00
|
|
|
cur = this.operatorize(cur, operator);
|
|
|
|
}
|
|
|
|
var nex = null;
|
|
|
|
while (!jQuery.isEmptyObject(tokens)) {
|
|
|
|
nex = tokens.shift();
|
2015-03-25 18:41:43 +01:00
|
|
|
if ((this.is_generator(nex)) && !test(nex)) {
|
2013-05-16 18:50:15 +02:00
|
|
|
nex = this.operatorize(nex, operator);
|
|
|
|
}
|
|
|
|
if (test(nex)) {
|
|
|
|
nex = tokens.shift();
|
|
|
|
while (test(nex)) {
|
|
|
|
nex = tokens.shift();
|
|
|
|
}
|
2015-03-25 18:41:43 +01:00
|
|
|
if (this.is_generator(nex)) {
|
2013-05-16 18:50:15 +02:00
|
|
|
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);
|
2015-03-25 18:41:43 +01:00
|
|
|
} else if ((cur !== null) && !test(cur)) {
|
2013-05-16 18:50:15 +02:00
|
|
|
result.push(cur);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
},
|
2015-04-24 13:56:41 +02:00
|
|
|
_clausify: function(e) {
|
|
|
|
e.clause = true;
|
|
|
|
return e;
|
|
|
|
},
|
2013-05-16 18:50:15 +02:00
|
|
|
parse_clause: function(tokens) {
|
|
|
|
var result = [];
|
|
|
|
tokens.forEach(function(clause) {
|
2015-03-25 18:41:43 +01:00
|
|
|
if (this.is_generator(clause)) {
|
|
|
|
result.concat(this.parse_clause(clause));
|
|
|
|
} else if ((clause == 'OR') || (clause == 'AND')) {
|
2013-05-16 18:50:15 +02:00
|
|
|
result.push(clause);
|
|
|
|
} else if ((clause.length == 1) &&
|
|
|
|
!(clause[0] instanceof Array)) {
|
2015-04-24 13:56:41 +02:00
|
|
|
result.push(this._clausify(['rec_name', 'ilike',
|
|
|
|
this.likify(clause[0])]));
|
2013-05-16 18:50:15 +02:00
|
|
|
} 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()];
|
2013-11-16 18:44:33 +01:00
|
|
|
|
|
|
|
var target = null;
|
|
|
|
if (field.type == 'reference') {
|
|
|
|
var split = this.split_target_value(field, value);
|
|
|
|
target = split[0];
|
|
|
|
value = split[1];
|
|
|
|
}
|
|
|
|
|
2016-02-02 19:09:55 +01:00
|
|
|
if (!operator) {
|
2013-05-16 18:50:15 +02:00
|
|
|
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));
|
|
|
|
}
|
2013-08-08 19:43:30 +02:00
|
|
|
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([
|
2015-04-24 13:56:41 +02:00
|
|
|
this._clausify([field.name, '>=', lvalue]),
|
|
|
|
this._clausify([field.name, '<=', rvalue])
|
2013-05-16 18:50:15 +02:00
|
|
|
]);
|
|
|
|
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);
|
|
|
|
}
|
2013-11-16 18:44:33 +01:00
|
|
|
if (operator.contains('like')) {
|
|
|
|
value = this.likify(value);
|
|
|
|
}
|
|
|
|
if (target) {
|
2015-04-24 13:56:41 +02:00
|
|
|
result.push(this._clausify([field.name + '.rec_name',
|
|
|
|
operator, value, target]));
|
2013-11-16 18:44:33 +01:00
|
|
|
} else {
|
2015-04-24 13:56:41 +02:00
|
|
|
result.push(this._clausify(
|
|
|
|
[field.name, operator, value]));
|
2013-11-16 18:44:33 +01:00
|
|
|
}
|
2013-05-16 18:50:15 +02:00
|
|
|
}
|
|
|
|
}.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;
|
|
|
|
}
|
2015-02-19 13:44:24 +01:00
|
|
|
if (value.contains('\\')) {
|
|
|
|
value = value.replace(new RegExp('\\\\', 'g'), '\\\\');
|
|
|
|
}
|
2014-10-14 15:22:31 +02:00
|
|
|
if (value.contains('"')) {
|
|
|
|
value = value.replace(new RegExp('"', 'g'), '\\"');
|
|
|
|
}
|
2013-05-16 18:50:15 +02:00
|
|
|
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) {
|
2013-11-16 18:44:33 +01:00
|
|
|
if (~['char', 'text', 'many2one', 'many2many', 'one2many',
|
|
|
|
'reference'].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';
|
|
|
|
}
|
|
|
|
},
|
2013-10-04 11:21:32 +02:00
|
|
|
time_format: function(field) {
|
|
|
|
return new Sao.PYSON.Decoder({}).decode(field.format);
|
|
|
|
},
|
2013-11-16 18:44:33 +01:00
|
|
|
split_target_value: function(field, value) {
|
|
|
|
var target = null;
|
|
|
|
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().startsWith(
|
|
|
|
text.toLowerCase() + ',')) {
|
|
|
|
target = key;
|
|
|
|
value = value.slice(text.length + 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return [target, value];
|
|
|
|
},
|
2013-05-16 18:50:15 +02:00
|
|
|
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') {
|
2015-05-29 10:44:10 +02:00
|
|
|
return [Sao.i18n.gettext('y'),
|
|
|
|
Sao.i18n.gettext('yes'),
|
|
|
|
Sao.i18n.gettext('true'),
|
|
|
|
Sao.i18n.gettext('t'),
|
|
|
|
'1'].some(
|
2013-05-16 18:50:15 +02:00
|
|
|
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,
|
2013-10-04 11:21:32 +02:00
|
|
|
'datetime': function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
var result = Sao.common.parse_datetime(
|
|
|
|
Sao.common.date_format(),
|
|
|
|
this.time_format(field),
|
|
|
|
value);
|
|
|
|
if (!result) {
|
|
|
|
result = Sao.common.parse_date(
|
2013-10-04 11:21:32 +02:00
|
|
|
Sao.common.date_format(),
|
|
|
|
value);
|
|
|
|
}
|
2015-02-06 00:32:51 +01:00
|
|
|
return result;
|
2013-10-04 11:21:32 +02:00
|
|
|
}.bind(this),
|
|
|
|
'date': function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
return Sao.common.parse_date(
|
|
|
|
Sao.common.date_format(),
|
|
|
|
value);
|
2013-10-04 11:21:32 +02:00
|
|
|
},
|
|
|
|
'time': function() {
|
|
|
|
try {
|
|
|
|
return Sao.common.parse_time(this.time_format(field),
|
|
|
|
value);
|
|
|
|
} catch (e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}.bind(this),
|
2015-07-09 15:32:45 +02:00
|
|
|
'timedelta': function() {
|
|
|
|
var converter = null;
|
|
|
|
if (field.converter) {
|
|
|
|
converter = this.context[field.converter];
|
|
|
|
}
|
|
|
|
return Sao.common.timedelta.parse(value, converter);
|
|
|
|
}.bind(this),
|
2013-05-16 18:50:15 +02:00
|
|
|
'many2one': function() {
|
|
|
|
if (value === '') {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
var func = converts[field.type];
|
|
|
|
if (func) {
|
|
|
|
return func();
|
|
|
|
} else {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
},
|
2013-11-16 18:44:33 +01:00
|
|
|
format_value: function(field, value, target) {
|
|
|
|
if (target === undefined) {
|
|
|
|
target = null;
|
|
|
|
}
|
2013-05-16 18:50:15 +02:00
|
|
|
var format_float = function() {
|
|
|
|
if (!value && value !== 0 && value !== new Sao.Decimal(0)) {
|
|
|
|
return '';
|
|
|
|
}
|
2013-09-04 10:42:23 +02:00
|
|
|
var digit = String(value).split('.')[1];
|
|
|
|
if (digit) {
|
|
|
|
digit = digit.length;
|
|
|
|
} else {
|
|
|
|
digit = 0;
|
2013-05-16 18:50:15 +02:00
|
|
|
}
|
|
|
|
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 || '';
|
|
|
|
};
|
|
|
|
|
2013-11-16 18:44:33 +01:00
|
|
|
var format_reference = function() {
|
|
|
|
if (!target) {
|
|
|
|
return format_selection();
|
|
|
|
}
|
|
|
|
for (var i = 0; i < field.selection.length; i++) {
|
|
|
|
if (field.selection[i][0] == target) {
|
|
|
|
target = field.selection[i][1];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return target + ',' + value;
|
|
|
|
};
|
|
|
|
|
2013-05-16 18:50:15 +02:00
|
|
|
var converts = {
|
|
|
|
'boolean': function() {
|
|
|
|
if (value) {
|
2015-05-29 10:44:10 +02:00
|
|
|
return Sao.i18n.gettext('True');
|
2013-05-16 18:50:15 +02:00
|
|
|
} else {
|
2015-05-29 10:44:10 +02:00
|
|
|
return Sao.i18n.gettext('False');
|
2013-05-16 18:50:15 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
'integer': function() {
|
|
|
|
if (value || value === 0) {
|
|
|
|
return '' + parseInt(value, 10);
|
|
|
|
} else {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'float': format_float,
|
|
|
|
'numeric': format_float,
|
|
|
|
'selection': format_selection,
|
2013-11-16 18:44:33 +01:00
|
|
|
'reference': format_reference,
|
2013-10-04 11:21:32 +02:00
|
|
|
'datetime': function() {
|
|
|
|
if (!value) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
if (value.isDate ||
|
2015-02-06 00:32:51 +01:00
|
|
|
!(value.hour() ||
|
|
|
|
value.minute() ||
|
|
|
|
value.second())) {
|
2015-01-26 11:32:47 +01:00
|
|
|
return Sao.common.format_date(
|
2013-10-04 11:21:32 +02:00
|
|
|
Sao.common.date_format(),
|
|
|
|
value);
|
|
|
|
}
|
|
|
|
return Sao.common.format_datetime(
|
|
|
|
Sao.common.date_format(),
|
|
|
|
this.time_format(field),
|
|
|
|
value);
|
|
|
|
}.bind(this),
|
|
|
|
'date': function() {
|
2015-01-26 11:32:47 +01:00
|
|
|
return Sao.common.format_date(
|
2013-10-04 11:21:32 +02:00
|
|
|
Sao.common.date_format(),
|
|
|
|
value);
|
|
|
|
},
|
|
|
|
'time': function() {
|
|
|
|
if (!value) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
return Sao.common.format_time(
|
|
|
|
this.time_format(field),
|
|
|
|
value);
|
|
|
|
}.bind(this),
|
2015-07-09 15:32:45 +02:00
|
|
|
'timedelta': function() {
|
|
|
|
if (!value || !value.valueOf()) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
var converter = null;
|
|
|
|
if (field.converter) {
|
|
|
|
converter = this.context[field.converter];
|
|
|
|
}
|
|
|
|
return Sao.common.timedelta.format(value, converter);
|
|
|
|
}.bind(this),
|
2013-05-16 18:50:15 +02:00
|
|
|
'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) {
|
2015-04-24 13:56:41 +02:00
|
|
|
if ((value instanceof Array) && !this.is_leaf(value)) {
|
2013-05-16 18:50:15 +02:00
|
|
|
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;}
|
|
|
|
},
|
2014-02-13 22:50:28 +01:00
|
|
|
locale_part: function(expression, field_name, locale_name) {
|
|
|
|
if (locale_name === undefined) {
|
|
|
|
locale_name = 'id';
|
|
|
|
}
|
2013-07-23 10:44:54 +02:00
|
|
|
if (expression === field_name) {
|
2014-02-13 22:50:28 +01:00
|
|
|
return locale_name;
|
2013-07-23 10:44:54 +02:00
|
|
|
}
|
|
|
|
if (expression.contains('.')) {
|
|
|
|
return expression.split('.').slice(1).join('.');
|
|
|
|
}
|
|
|
|
return expression;
|
|
|
|
},
|
|
|
|
is_leaf: function(expression) {
|
|
|
|
return ((expression instanceof Array) &&
|
|
|
|
(expression.length > 2) &&
|
2015-05-15 15:29:07 +02:00
|
|
|
(typeof expression[1] == 'string'));
|
2013-07-23 10:44:54 +02:00
|
|
|
},
|
|
|
|
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]]);
|
|
|
|
}
|
2014-05-17 18:31:20 +02:00
|
|
|
if ((operand == '=') &&
|
|
|
|
(context[field] === null || context[field] === undefined) &&
|
|
|
|
(boolop === this.and)) {
|
2013-07-23 10:44:54 +02:00
|
|
|
// We should consider that other domain inversion will set a
|
|
|
|
// correct value to this field
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
var context_field = context[field];
|
2015-03-27 15:41:05 +01:00
|
|
|
if ((context_field && context_field._isAMomentObject) && !value) {
|
|
|
|
if (context_field.isDateTime) {
|
|
|
|
value = Sao.DateTime.min;
|
|
|
|
} else {
|
|
|
|
value = Sao.Date.min;
|
|
|
|
}
|
2013-07-23 10:44:54 +02:00
|
|
|
}
|
2015-03-27 15:41:05 +01:00
|
|
|
if ((value && value._isAMomentObject) && !context_field) {
|
|
|
|
if (value.isDateTime) {
|
|
|
|
context_field = Sao.DateTime.min;
|
|
|
|
} else {
|
|
|
|
context_field = Sao.Date.min;
|
|
|
|
}
|
2013-07-23 10:44:54 +02:00
|
|
|
}
|
|
|
|
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(',');
|
|
|
|
}
|
2014-02-13 22:50:28 +01:00
|
|
|
if (~['=', '!='].indexOf(operand) &&
|
|
|
|
context_field instanceof Array &&
|
|
|
|
typeof value == 'number') {
|
|
|
|
operand = {
|
|
|
|
'=': 'in',
|
|
|
|
'!=': 'not in'
|
|
|
|
}[operand];
|
|
|
|
}
|
2015-05-15 13:50:52 +02:00
|
|
|
if (operand in this.OPERATORS) {
|
|
|
|
return this.OPERATORS[operand](context_field, value);
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
2013-07-23 10:44:54 +02:00
|
|
|
},
|
|
|
|
inverse_leaf: function(domain) {
|
2013-08-08 19:43:30 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
},
|
2015-03-30 22:37:52 +02:00
|
|
|
filter_leaf: function(domain, field, model) {
|
|
|
|
if (~['AND', 'OR'].indexOf(domain)) {
|
|
|
|
return domain;
|
|
|
|
} else if (this.is_leaf(domain)) {
|
|
|
|
if (domain[0].startsWith(field) && (domain.length > 3)) {
|
|
|
|
if (domain[3] !== model) {
|
|
|
|
return ['id', '=', null];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return domain;
|
|
|
|
} else {
|
|
|
|
return domain.map(function(d) {
|
|
|
|
return this.filter_leaf(d, field, model);
|
|
|
|
}.bind(this));
|
|
|
|
}
|
|
|
|
},
|
2013-07-23 10:44:54 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
},
|
2015-09-22 22:54:41 +02:00
|
|
|
localize_domain: function(domain, field_name, strip_target) {
|
2013-08-08 19:43:30 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
2014-02-13 22:50:28 +01:00
|
|
|
var local_name = 'id';
|
|
|
|
if (typeof domain[2] == 'string') {
|
|
|
|
local_name = 'rec_name';
|
|
|
|
}
|
2015-09-22 22:54:41 +02:00
|
|
|
var n = strip_target ? 3 : 4;
|
2014-02-13 22:50:28 +01:00
|
|
|
return [this.locale_part(domain[0], field_name, local_name)]
|
2015-09-22 22:54:41 +02:00
|
|
|
.concat(domain.slice(1, n)).concat(domain.slice(4));
|
2013-07-23 10:44:54 +02:00
|
|
|
} else {
|
|
|
|
return domain.map(function(e) {
|
2015-09-22 22:54:41 +02:00
|
|
|
return this.localize_domain(e, field_name, strip_target);
|
2013-07-23 10:44:54 +02:00
|
|
|
}.bind(this));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
simplify: function(domain) {
|
|
|
|
if (this.is_leaf(domain)) {
|
|
|
|
return domain;
|
2013-08-08 19:43:30 +02:00
|
|
|
} else if (~['OR', 'AND'].indexOf(domain)) {
|
2013-07-23 10:44:54 +02:00
|
|
|
return domain;
|
2014-06-08 00:24:51 +02:00
|
|
|
} else if ((domain instanceof Array) && (domain.length == 1) &&
|
|
|
|
(~['OR', 'AND'].indexOf(domain[0]))) {
|
|
|
|
return [];
|
2013-07-23 10:44:54 +02:00
|
|
|
} 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) &&
|
2013-08-08 19:43:30 +02:00
|
|
|
~['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) ||
|
2013-08-08 19:43:30 +02:00
|
|
|
~['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) {
|
2013-10-07 12:22:39 +02:00
|
|
|
return [domain_type].concat([].concat.apply([],
|
2013-07-23 10:44:54 +02:00
|
|
|
domain.map(function(e) {
|
|
|
|
return this.merge(e, domain_type);
|
|
|
|
}.bind(this))));
|
|
|
|
} else if (domain_type == domoperator) {
|
2013-10-07 12:22:39 +02:00
|
|
|
return [].concat.apply([], domain.map(function(e) {
|
2013-07-23 10:44:54 +02:00
|
|
|
return this.merge(e, domain_type);
|
|
|
|
}.bind(this)));
|
|
|
|
} else {
|
|
|
|
// without setting the domoperator
|
|
|
|
return [this.merge(domain)];
|
|
|
|
}
|
|
|
|
},
|
2014-03-31 19:03:42 +02:00
|
|
|
concat: function(domains, domoperator) {
|
|
|
|
var result = [];
|
|
|
|
if (domoperator) {
|
|
|
|
result.push(domoperator);
|
|
|
|
}
|
|
|
|
domains.forEach(function append(domain) {
|
|
|
|
if (!jQuery.isEmptyObject(domain)) {
|
|
|
|
result.push(domain);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return this.simplify(this.merge(result));
|
|
|
|
},
|
2015-03-18 11:10:29 +01:00
|
|
|
unique_value: function(domain) {
|
|
|
|
if ((domain instanceof Array) &&
|
|
|
|
(domain.length == 1) &&
|
2015-03-30 22:40:07 +02:00
|
|
|
!domain[0][0].contains('.') &&
|
2015-03-18 11:10:29 +01:00
|
|
|
(domain[0][1] == '=')) {
|
|
|
|
return [true, domain[0][1], domain[0][2]];
|
|
|
|
} else {
|
|
|
|
return [false, null, null];
|
|
|
|
}
|
|
|
|
},
|
2013-07-23 10:44:54 +02:00
|
|
|
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);
|
2013-08-08 19:43:30 +02:00
|
|
|
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) {
|
2014-02-13 22:50:28 +01:00
|
|
|
if (b instanceof Array) {
|
|
|
|
for (var i = 0, len = a.length; i < len; i++) {
|
|
|
|
if (~b.indexOf(a[i])) {
|
|
|
|
return true;
|
|
|
|
}
|
2013-07-23 10:44:54 +02:00
|
|
|
}
|
2014-02-13 22:50:28 +01:00
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return Boolean(~a.indexOf(b));
|
2013-07-23 10:44:54 +02:00
|
|
|
}
|
|
|
|
} else {
|
2013-08-08 19:43:30 +02:00
|
|
|
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 = [];
|
2013-08-08 19:43:30 +02:00
|
|
|
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-08-08 19:43:30 +02:00
|
|
|
}))) {
|
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';
|
2013-08-08 19:43:30 +02:00
|
|
|
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-08-23 14:22:40 +02:00
|
|
|
|
|
|
|
Sao.common.guess_mimetype = function(filename) {
|
|
|
|
if (/.*odt$/.test(filename)) {
|
|
|
|
return 'application/vnd.oasis.opendocument.text';
|
|
|
|
} else if (/.*ods$/.test(filename)) {
|
|
|
|
return 'application/vnd.oasis.opendocument.spreadsheet';
|
|
|
|
} else if (/.*pdf$/.test(filename)) {
|
|
|
|
return 'application/pdf';
|
|
|
|
} else if (/.*docx$/.test(filename)) {
|
|
|
|
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
|
|
|
} else if (/.*doc/.test(filename)) {
|
|
|
|
return 'application/msword';
|
|
|
|
} else if (/.*xlsx$/.test(filename)) {
|
|
|
|
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
|
|
|
} else if (/.*xls/.test(filename)) {
|
|
|
|
return 'application/vnd.ms-excel';
|
|
|
|
} else {
|
|
|
|
return 'application/octet-binary';
|
|
|
|
}
|
|
|
|
};
|
2013-08-28 18:07:43 +02:00
|
|
|
|
|
|
|
Sao.common.LOCAL_ICONS = [
|
|
|
|
'tryton-attachment-hi',
|
|
|
|
'tryton-attachment',
|
|
|
|
'tryton-bookmark',
|
2014-05-19 21:06:11 +02:00
|
|
|
'tryton-cancel',
|
2013-08-28 18:07:43 +02:00
|
|
|
'tryton-clear',
|
|
|
|
'tryton-close',
|
|
|
|
'tryton-connect',
|
|
|
|
'tryton-copy',
|
|
|
|
'tryton-delete',
|
|
|
|
'tryton-dialog-error',
|
|
|
|
'tryton-dialog-information',
|
|
|
|
'tryton-dialog-warning',
|
|
|
|
'tryton-disconnect',
|
|
|
|
'tryton-executable',
|
|
|
|
'tryton-find-replace',
|
|
|
|
'tryton-find',
|
|
|
|
'tryton-folder-new',
|
|
|
|
'tryton-fullscreen',
|
|
|
|
'tryton-go-home',
|
|
|
|
'tryton-go-jump',
|
|
|
|
'tryton-go-next',
|
|
|
|
'tryton-go-previous',
|
|
|
|
'tryton-help',
|
|
|
|
'tryton-icon',
|
|
|
|
'tryton-list-add',
|
|
|
|
'tryton-list-remove',
|
|
|
|
'tryton-locale',
|
|
|
|
'tryton-lock',
|
|
|
|
'tryton-log-out',
|
|
|
|
'tryton-mail-message-new',
|
|
|
|
'tryton-mail-message',
|
|
|
|
'tryton-new',
|
2014-05-19 21:06:11 +02:00
|
|
|
'tryton-ok',
|
2013-08-28 18:07:43 +02:00
|
|
|
'tryton-open',
|
|
|
|
'tryton-preferences-system-session',
|
|
|
|
'tryton-preferences-system',
|
|
|
|
'tryton-preferences',
|
|
|
|
'tryton-print-email',
|
|
|
|
'tryton-print-open',
|
|
|
|
'tryton-print',
|
|
|
|
'tryton-refresh',
|
|
|
|
'tryton-save-as',
|
|
|
|
'tryton-save',
|
|
|
|
'tryton-star',
|
|
|
|
'tryton-start-here',
|
|
|
|
'tryton-system-file-manager',
|
|
|
|
'tryton-system',
|
|
|
|
'tryton-text-background',
|
|
|
|
'tryton-text-foreground',
|
|
|
|
'tryton-text-markup',
|
|
|
|
'tryton-undo',
|
|
|
|
'tryton-unstar',
|
|
|
|
'tryton-web-browser'
|
|
|
|
];
|
|
|
|
|
|
|
|
Sao.common.IconFactory = Sao.class_(Object, {
|
|
|
|
batchnum: 10,
|
|
|
|
name2id: {},
|
|
|
|
loaded_icons: {},
|
|
|
|
tryton_icons: [],
|
2014-05-27 22:34:46 +02:00
|
|
|
register_prm: jQuery.when(),
|
2013-08-28 18:07:43 +02:00
|
|
|
load_icons: function(refresh) {
|
|
|
|
refresh = refresh || false;
|
|
|
|
if (!refresh) {
|
|
|
|
for (var icon_name in this.load_icons) {
|
|
|
|
if (!this.load_icons.hasOwnProperty(icon_name)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
window.URL.revokeObjectURL(this.load_icons[icon_name]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var icon_model = new Sao.Model('ir.ui.icon');
|
2014-05-27 22:34:46 +02:00
|
|
|
return icon_model.execute('list_icons', [], {})
|
|
|
|
.then(function(icons) {
|
|
|
|
if (!refresh) {
|
|
|
|
this.name2id = {};
|
|
|
|
this.loaded_icons = {};
|
|
|
|
}
|
|
|
|
this.tryton_icons = [];
|
|
|
|
|
2013-08-28 18:07:43 +02:00
|
|
|
var icon_id, icon_name;
|
|
|
|
for (var i=0, len=icons.length; i < len; i++) {
|
|
|
|
icon_id = icons[i][0];
|
|
|
|
icon_name = icons[i][1];
|
|
|
|
if (refresh && (icon_name in this.loaded_icons)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this.tryton_icons.push([icon_id, icon_name]);
|
|
|
|
this.name2id[icon_name] = icon_id;
|
|
|
|
}
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
register_icon: function(icon_name) {
|
|
|
|
if (!icon_name) {
|
|
|
|
return jQuery.when('');
|
|
|
|
} else if ((icon_name in this.loaded_icons) ||
|
|
|
|
~Sao.common.LOCAL_ICONS.indexOf(icon_name)) {
|
|
|
|
return jQuery.when(this.get_icon_url(icon_name));
|
|
|
|
}
|
2014-05-27 22:34:46 +02:00
|
|
|
if (this.register_prm.state() == 'pending') {
|
|
|
|
var waiting_prm = jQuery.Deferred();
|
|
|
|
this.register_prm.then(function() {
|
|
|
|
this.register_icon(icon_name).then(
|
|
|
|
waiting_prm.resolve, waiting_prm.reject);
|
|
|
|
}.bind(this));
|
|
|
|
return waiting_prm;
|
|
|
|
}
|
2013-08-28 18:07:43 +02:00
|
|
|
var loaded_prm;
|
|
|
|
if (!(icon_name in this.name2id)) {
|
|
|
|
loaded_prm = this.load_icons(true);
|
|
|
|
} else {
|
|
|
|
loaded_prm = jQuery.when();
|
|
|
|
}
|
|
|
|
|
|
|
|
var icon_model = new Sao.Model('ir.ui.icon');
|
2014-05-27 22:34:46 +02:00
|
|
|
this.register_prm = loaded_prm.then(function () {
|
2013-08-28 18:07:43 +02:00
|
|
|
var find_array = function(array) {
|
|
|
|
var idx, l;
|
|
|
|
for (idx=0, l=this.tryton_icons.length; idx < l; idx++) {
|
|
|
|
var icon = this.tryton_icons[idx];
|
|
|
|
if (Sao.common.compare(icon, array)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return idx;
|
|
|
|
}.bind(this);
|
|
|
|
var idx = find_array([this.name2id[icon_name], icon_name]);
|
|
|
|
var from = Math.round(idx - this.batchnum / 2);
|
|
|
|
from = (from < 0) ? 0 : from;
|
|
|
|
var to = Math.round(idx + this.batchnum / 2);
|
|
|
|
var ids = [];
|
|
|
|
this.tryton_icons.slice(from, to).forEach(function(e) {
|
|
|
|
ids.push(e[0]);
|
|
|
|
});
|
|
|
|
|
|
|
|
var read_prm = icon_model.execute('read',
|
2014-05-27 22:34:46 +02:00
|
|
|
[ids, ['name', 'icon']], {});
|
2013-08-28 18:07:43 +02:00
|
|
|
return read_prm.then(function(icons) {
|
|
|
|
icons.forEach(function(icon) {
|
2015-09-25 18:03:02 +02:00
|
|
|
var img_url;
|
|
|
|
if (navigator.userAgent.match(/firefox/i)) {
|
|
|
|
// Fixefox doesn't support SVG inside Blob
|
|
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=841920
|
|
|
|
// Temporary use the embeded base64 version which
|
|
|
|
// will be replaced later by the URL of the png object
|
|
|
|
img_url = 'data:image/svg+xml;base64,' +
|
|
|
|
window.btoa(unescape(encodeURIComponent(icon.icon)));
|
|
|
|
var image = new Image();
|
|
|
|
image.src = img_url;
|
|
|
|
image.onload = function() {
|
|
|
|
var canvas = document.createElement('canvas');
|
|
|
|
canvas.width = image.width;
|
|
|
|
canvas.height = image.height;
|
|
|
|
var context = canvas.getContext('2d');
|
|
|
|
context.drawImage(image, 0, 0);
|
|
|
|
canvas.toBlob(function(blob) {
|
|
|
|
var old_img_url = img_url;
|
|
|
|
img_url = window.URL.createObjectURL(blob);
|
|
|
|
this.loaded_icons[icon.name] = img_url;
|
|
|
|
jQuery(document).find('img').each(function(i, el) {
|
|
|
|
if (el.src == old_img_url) {
|
|
|
|
el.src = img_url;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
canvas.remove();
|
|
|
|
}.bind(this), 'image/png');
|
|
|
|
}.bind(this);
|
|
|
|
} else {
|
|
|
|
var blob = new Blob([icon.icon],
|
|
|
|
{type: 'image/svg+xml'});
|
|
|
|
img_url = window.URL.createObjectURL(blob);
|
|
|
|
}
|
2013-08-28 18:07:43 +02:00
|
|
|
this.loaded_icons[icon.name] = img_url;
|
|
|
|
delete this.name2id[icon.name];
|
|
|
|
this.tryton_icons.splice(
|
|
|
|
find_array([icon.id, icon.name]), 1);
|
|
|
|
}.bind(this));
|
|
|
|
return this.get_icon_url(icon_name);
|
|
|
|
}.bind(this));
|
|
|
|
}.bind(this));
|
2014-05-27 22:34:46 +02:00
|
|
|
return this.register_prm;
|
2013-08-28 18:07:43 +02:00
|
|
|
},
|
|
|
|
get_icon_url: function(icon_name) {
|
|
|
|
if (icon_name in this.loaded_icons) {
|
|
|
|
return this.loaded_icons[icon_name];
|
|
|
|
}
|
|
|
|
return "images/" + icon_name + ".svg";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Sao.common.ICONFACTORY = new Sao.common.IconFactory();
|
2013-10-14 11:02:47 +02:00
|
|
|
|
|
|
|
Sao.common.UniqueDialog = Sao.class_(Object, {
|
|
|
|
init: function() {
|
|
|
|
this.running = false;
|
|
|
|
},
|
|
|
|
build_dialog: function() {
|
2015-03-27 11:49:22 +01:00
|
|
|
var dialog = new Sao.Dialog('', this.class_);
|
2015-02-06 00:32:51 +01:00
|
|
|
return dialog;
|
2013-10-14 11:02:47 +02:00
|
|
|
},
|
|
|
|
run: function() {
|
|
|
|
if (this.running) {
|
|
|
|
return;
|
|
|
|
}
|
2013-10-14 11:15:27 +02:00
|
|
|
var args = Array.prototype.slice.call(arguments);
|
2013-10-14 11:02:47 +02:00
|
|
|
var prm = jQuery.Deferred();
|
|
|
|
args.push(prm);
|
|
|
|
var dialog = this.build_dialog.apply(this, args);
|
2016-04-28 20:57:37 +02:00
|
|
|
dialog.content.submit(function(evt) {
|
|
|
|
dialog.footer.find('button.btn-primary').first().click();
|
|
|
|
evt.preventDefault();
|
|
|
|
}.bind(this));
|
2013-10-14 11:02:47 +02:00
|
|
|
this.running = true;
|
2015-03-27 11:49:22 +01:00
|
|
|
dialog.modal.modal('show');
|
2016-04-28 20:57:37 +02:00
|
|
|
dialog.modal.on('shown.bs.modal', function() {
|
|
|
|
dialog.modal.find('input,select')
|
|
|
|
.filter(':visible').first().focus();
|
|
|
|
});
|
2013-10-14 11:02:47 +02:00
|
|
|
return prm;
|
|
|
|
},
|
|
|
|
close: function(dialog) {
|
2015-03-27 11:49:22 +01:00
|
|
|
dialog.modal.on('hidden.bs.modal', function(event) {
|
2015-03-24 21:13:50 +01:00
|
|
|
jQuery(this).remove();
|
|
|
|
});
|
2015-03-27 11:49:22 +01:00
|
|
|
dialog.modal.modal('hide');
|
2013-10-14 11:02:47 +02:00
|
|
|
this.running = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Sao.common.MessageDialog = Sao.class_(Sao.common.UniqueDialog, {
|
|
|
|
class_: 'message-dialog',
|
|
|
|
build_dialog: function(message, icon, prm) {
|
2015-02-06 00:32:51 +01:00
|
|
|
var dialog = Sao.common.MessageDialog._super.build_dialog.call(
|
|
|
|
this);
|
2015-03-27 11:49:22 +01:00
|
|
|
dialog.header.remove();
|
|
|
|
dialog.body.append(jQuery('<div/>', {
|
2015-02-06 00:32:51 +01:00
|
|
|
'class': 'alert alert-info',
|
|
|
|
role: 'alert'
|
|
|
|
}).append(jQuery('<span/>', {
|
|
|
|
'class': 'glyphicon ' + icon,
|
|
|
|
'aria-hidden': true
|
|
|
|
})).append(jQuery('<span/>', {
|
|
|
|
'class': 'sr-only'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Message: '))
|
2015-09-06 21:19:17 +02:00
|
|
|
).append(jQuery('<span/>')
|
|
|
|
.append(message)
|
|
|
|
.css('white-space', 'pre-wrap')));
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-primary',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('OK')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.resolve('ok');
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2013-10-14 11:02:47 +02:00
|
|
|
return dialog;
|
|
|
|
},
|
|
|
|
run: function(message, icon) {
|
2015-02-06 00:32:51 +01:00
|
|
|
return Sao.common.MessageDialog._super.run.call(
|
|
|
|
this, message, icon || 'glyphicon-info-sign');
|
2013-10-14 11:02:47 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.message = new Sao.common.MessageDialog();
|
2013-11-14 15:53:49 +01:00
|
|
|
|
2013-11-25 12:18:32 +01:00
|
|
|
Sao.common.WarningDialog = Sao.class_(Sao.common.UniqueDialog, {
|
|
|
|
class_: 'warning-dialog',
|
|
|
|
build_dialog: function(message, title, prm) {
|
2015-02-06 00:32:51 +01:00
|
|
|
var dialog = Sao.common.WarningDialog._super.build_dialog.call(
|
|
|
|
this);
|
2015-03-27 11:49:22 +01:00
|
|
|
dialog.add_title(title);
|
|
|
|
dialog.body.append(jQuery('<div/>', {
|
2015-02-06 00:32:51 +01:00
|
|
|
'class': 'alert alert-warning',
|
|
|
|
role: 'alert'
|
|
|
|
}).append(jQuery('<span/>', {
|
|
|
|
'class': 'glyphicon glyphicon-alert',
|
|
|
|
'aria-hidden': true
|
|
|
|
})).append(jQuery('<span/>', {
|
|
|
|
'class': 'sr-only'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Warning: '))
|
2015-09-06 21:19:17 +02:00
|
|
|
).append(jQuery('<span/>')
|
|
|
|
.append(message)
|
|
|
|
.css('white-space', 'pre-wrap')));
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-primary',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('OK')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.resolve('ok');
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2013-11-25 12:18:32 +01:00
|
|
|
return dialog;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.warning = new Sao.common.WarningDialog();
|
|
|
|
|
|
|
|
Sao.common.UserWarningDialog = Sao.class_(Sao.common.WarningDialog, {
|
|
|
|
class_: 'user-warning-dialog',
|
|
|
|
always: false,
|
|
|
|
_set_always: function() {
|
|
|
|
this.always = jQuery(this).prop('checked');
|
|
|
|
},
|
|
|
|
build_dialog: function(message, title, prm) {
|
|
|
|
var dialog = Sao.common.UserWarningDialog._super.build_dialog.call(
|
|
|
|
this, message, title, prm);
|
2015-03-27 11:49:22 +01:00
|
|
|
dialog.body.append(jQuery('<div/>')
|
2013-11-25 12:18:32 +01:00
|
|
|
.append(jQuery('<input/>', {
|
|
|
|
'type': 'checkbox'
|
|
|
|
}).change(this._set_always.bind(this)))
|
2015-05-29 10:44:10 +02:00
|
|
|
.append(jQuery('<span/>')
|
|
|
|
.text(Sao.i18n.gettext('Always ignore this warning.')))
|
2013-11-25 12:18:32 +01:00
|
|
|
);
|
2015-05-29 10:44:10 +02:00
|
|
|
dialog.body.append(jQuery('<p/>')
|
|
|
|
.text(Sao.i18n.gettext('Do you want to proceed?')));
|
2015-03-27 13:10:28 +01:00
|
|
|
dialog.footer.children().remove();
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-link',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('No')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.reject();
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-primary',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Yes')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
if (this.always) {
|
|
|
|
prm.resolve('always');
|
|
|
|
}
|
|
|
|
prm.resolve('ok');
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2013-11-25 12:18:32 +01:00
|
|
|
return dialog;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.userwarning = new Sao.common.UserWarningDialog();
|
|
|
|
|
|
|
|
Sao.common.ConfirmationDialog = Sao.class_(Sao.common.UniqueDialog, {
|
|
|
|
class_: 'confirmation-dialog',
|
|
|
|
build_dialog: function(message) {
|
2015-02-06 00:32:51 +01:00
|
|
|
var dialog = Sao.common.ConfirmationDialog._super.build_dialog.call(
|
|
|
|
this);
|
2015-03-27 11:49:22 +01:00
|
|
|
dialog.header.remove();
|
|
|
|
dialog.body.append(jQuery('<div/>', {
|
2015-02-06 00:32:51 +01:00
|
|
|
'class': 'alert alert-info',
|
|
|
|
role: 'alert'
|
|
|
|
}).append(jQuery('<span/>', {
|
|
|
|
'class': 'glyphicon glyphicon-info-sign',
|
|
|
|
'aria-hidden': true
|
|
|
|
})).append(jQuery('<span/>', {
|
|
|
|
'class': 'sr-only'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Confirmation: '))
|
2015-09-06 21:19:17 +02:00
|
|
|
).append(jQuery('<span/>')
|
|
|
|
.append(message)
|
|
|
|
.css('white-space', 'pre-wrap')));
|
2013-11-25 12:18:32 +01:00
|
|
|
return dialog;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Sao.common.SurDialog = Sao.class_(Sao.common.ConfirmationDialog, {
|
|
|
|
build_dialog: function(message, prm) {
|
|
|
|
var dialog = Sao.common.SurDialog._super.build_dialog.call(
|
|
|
|
this, message);
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-link',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Cancel')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.reject();
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-primary',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('OK')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.resolve();
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2013-11-25 12:18:32 +01:00
|
|
|
return dialog;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.sur = new Sao.common.SurDialog();
|
|
|
|
|
|
|
|
Sao.common.Sur3BDialog = Sao.class_(Sao.common.ConfirmationDialog, {
|
|
|
|
build_dialog: function(message, prm) {
|
|
|
|
var dialog = Sao.common.SurDialog._super.build_dialog.call(
|
|
|
|
this, message);
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-link',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Cancel')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.resolve('cancel');
|
2015-03-27 13:10:28 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-default',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('No')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.resolve('ko');
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-primary',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Yes')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.resolve('ok');
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2013-11-25 12:18:32 +01:00
|
|
|
return dialog;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.sur_3b = new Sao.common.Sur3BDialog();
|
|
|
|
|
|
|
|
Sao.common.AskDialog = Sao.class_(Sao.common.UniqueDialog, {
|
|
|
|
class_: 'ask-dialog',
|
|
|
|
run: function() {
|
|
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
if (args.length == 1) {
|
|
|
|
args.push(true);
|
|
|
|
}
|
|
|
|
return Sao.common.AskDialog._super.run.apply(this, args);
|
|
|
|
},
|
|
|
|
build_dialog: function(question, visibility, prm) {
|
2015-02-06 00:32:51 +01:00
|
|
|
var dialog = Sao.common.AskDialog._super.build_dialog.call(this);
|
2015-03-27 11:49:22 +01:00
|
|
|
dialog.header.remove();
|
2013-11-25 12:18:32 +01:00
|
|
|
var entry = jQuery('<input/>', {
|
2015-02-06 00:32:51 +01:00
|
|
|
'class': 'form-control',
|
|
|
|
'type': visibility ? 'input' : 'password',
|
|
|
|
'id': 'ask-dialog-entry'
|
2013-11-25 12:18:32 +01:00
|
|
|
});
|
2015-12-13 14:36:11 +01:00
|
|
|
dialog.body.append(jQuery('<div/>', {
|
2015-02-06 00:32:51 +01:00
|
|
|
'class': 'form-group'
|
|
|
|
}).append(jQuery('<label/>', {
|
|
|
|
'for': 'ask-dialog-entry'
|
|
|
|
}).append(question)).append(entry));
|
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-link',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Cancel')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.reject();
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-primary',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('OK')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.resolve(entry.val());
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2013-11-25 12:18:32 +01:00
|
|
|
return dialog;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.ask = new Sao.common.AskDialog();
|
|
|
|
|
|
|
|
Sao.common.ConcurrencyDialog = Sao.class_(Sao.common.UniqueDialog, {
|
|
|
|
class_: 'ask-dialog',
|
|
|
|
build_dialog: function(model, record_id, context, prm) {
|
2015-02-06 00:32:51 +01:00
|
|
|
var dialog = Sao.common.ConcurrencyDialog._super.build_dialog.call(
|
|
|
|
this);
|
2015-03-27 13:10:28 +01:00
|
|
|
dialog.modal.find('.modal-dialog'
|
2015-02-06 00:32:51 +01:00
|
|
|
).removeClass('modal-sm').addClass('modal-lg');
|
2015-05-29 10:44:10 +02:00
|
|
|
dialog.add_title(Sao.i18n.gettext('Concurrency Exception'));
|
2015-03-27 11:49:22 +01:00
|
|
|
dialog.body.append(jQuery('<div/>', {
|
2015-02-06 00:32:51 +01:00
|
|
|
'class': 'alert alert-warning',
|
|
|
|
role: 'alert'
|
|
|
|
}).append(jQuery('<p/>')
|
|
|
|
.append(jQuery('<span/>', {
|
|
|
|
'class': 'glyphicon glyphicon-info-sign',
|
|
|
|
'aria-hidden': true
|
|
|
|
})).append(jQuery('<span/>', {
|
|
|
|
'class': 'sr-only'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Write Concurrency Warning: '))
|
|
|
|
).append(Sao.i18n.gettext('This record has been modified ' +
|
|
|
|
'while you were editing it.')))
|
|
|
|
.append(jQuery('<p/>').text(Sao.i18n.gettext('Choose:')))
|
2013-11-25 12:18:32 +01:00
|
|
|
.append(jQuery('<ul/>')
|
|
|
|
.append(jQuery('<li/>')
|
2015-05-29 10:44:10 +02:00
|
|
|
.text(Sao.i18n.gettext('"Cancel" to cancel saving;')))
|
2013-11-25 12:18:32 +01:00
|
|
|
.append(jQuery('<li/>')
|
2015-05-29 10:44:10 +02:00
|
|
|
.text(Sao.i18n.gettext(
|
|
|
|
'"Compare" to see the modified version;')))
|
2013-11-25 12:18:32 +01:00
|
|
|
.append(jQuery('<li/>')
|
2015-05-29 10:44:10 +02:00
|
|
|
.text(Sao.i18n.gettext(
|
|
|
|
'"Write Anyway" to save your current version.'))))
|
2013-11-25 12:18:32 +01:00
|
|
|
);
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-link',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Cancel')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.reject();
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-default',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Compare')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
Sao.Tab.create({
|
|
|
|
'model': model,
|
|
|
|
'res_id': record_id,
|
|
|
|
'domain': [['id', '=', record_id]],
|
|
|
|
'context': context,
|
|
|
|
'mode': ['form', 'tree']
|
|
|
|
});
|
|
|
|
prm.reject();
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-default',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Write Anyway')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.resolve();
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2013-11-25 12:18:32 +01:00
|
|
|
return dialog;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.concurrency = new Sao.common.ConcurrencyDialog();
|
|
|
|
|
|
|
|
Sao.common.ErrorDialog = Sao.class_(Sao.common.UniqueDialog, {
|
|
|
|
class_: 'error-dialog',
|
|
|
|
build_dialog: function(title, details, prm) {
|
2015-02-06 00:32:51 +01:00
|
|
|
var dialog = Sao.common.ConcurrencyDialog._super.build_dialog.call(
|
|
|
|
this);
|
2015-03-27 13:10:28 +01:00
|
|
|
dialog.modal.find('.modal-dialog'
|
2015-02-06 00:32:51 +01:00
|
|
|
).removeClass('modal-sm').addClass('modal-lg');
|
2015-05-29 10:44:10 +02:00
|
|
|
dialog.add_title(Sao.i18n.gettext('Application Error'));
|
2015-03-27 11:49:22 +01:00
|
|
|
dialog.body.append(jQuery('<div/>', {
|
2015-02-06 00:32:51 +01:00
|
|
|
'class': 'alert alert-warning',
|
|
|
|
role: 'alert'
|
|
|
|
}).append(jQuery('<span/>', {
|
|
|
|
'class': 'glyphicon glyphicon-alert',
|
|
|
|
'aria-hidden': true
|
|
|
|
})).append(jQuery('<span/>', {
|
|
|
|
'class': 'sr-only'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Warning: '))
|
2015-02-06 00:32:51 +01:00
|
|
|
).append(jQuery('<p/>')
|
|
|
|
.append(jQuery('<pre/>')
|
|
|
|
.text(details)))
|
2013-11-25 12:18:32 +01:00
|
|
|
.append(jQuery('<p/>')
|
|
|
|
.append(jQuery('<a/>', {
|
2015-02-06 00:32:51 +01:00
|
|
|
'class': 'btn btn-link',
|
2013-11-25 12:18:32 +01:00
|
|
|
href: Sao.config.roundup.url,
|
|
|
|
target: '_blank'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).text(Sao.i18n.gettext('Report Bug')))));
|
2015-02-06 00:32:51 +01:00
|
|
|
jQuery('<button/>', {
|
2015-05-07 12:11:06 +02:00
|
|
|
'class': 'btn btn-primary',
|
|
|
|
'type': 'button'
|
2015-05-29 10:44:10 +02:00
|
|
|
}).append(Sao.i18n.gettext('Close')).click(function() {
|
2015-02-06 00:32:51 +01:00
|
|
|
this.close(dialog);
|
|
|
|
prm.resolve();
|
2015-03-27 11:49:22 +01:00
|
|
|
}.bind(this)).appendTo(dialog.footer);
|
2013-11-25 12:18:32 +01:00
|
|
|
return dialog;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.error = new Sao.common.ErrorDialog();
|
2015-02-20 11:30:11 +01:00
|
|
|
|
|
|
|
Sao.common.Processing = Sao.class_(Object, {
|
|
|
|
queries: 0,
|
2015-02-28 01:21:30 +01:00
|
|
|
timeout: 500,
|
2015-02-20 11:30:11 +01:00
|
|
|
init: function() {
|
|
|
|
this.el = jQuery('<div/>', {
|
|
|
|
'id': 'processing',
|
|
|
|
'class': 'text-center'
|
|
|
|
});
|
|
|
|
this.el.append(jQuery('<span/>', {
|
|
|
|
'class': 'label label-info',
|
2015-05-29 10:44:10 +02:00
|
|
|
'text': 'Processing...'
|
2015-02-20 11:30:11 +01:00
|
|
|
}));
|
|
|
|
this.el.hide();
|
|
|
|
jQuery(function() {
|
|
|
|
this.el.appendTo('body');
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
show: function() {
|
|
|
|
return window.setTimeout(function() {
|
|
|
|
this.queries += 1;
|
|
|
|
this.el.show();
|
|
|
|
}.bind(this), this.timeout);
|
|
|
|
},
|
|
|
|
hide: function(timeoutID) {
|
|
|
|
window.clearTimeout(timeoutID);
|
|
|
|
if (this.queries > 0) {
|
|
|
|
this.queries -= 1;
|
|
|
|
}
|
|
|
|
if (this.queries <= 0) {
|
|
|
|
this.queries = 0;
|
2015-02-20 14:41:26 +01:00
|
|
|
this.el.hide();
|
2015-02-20 11:30:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Sao.common.processing = new Sao.common.Processing();
|
2015-03-13 16:45:47 +01:00
|
|
|
|
2015-10-02 11:49:23 +02:00
|
|
|
Sao.common.InputCompletion = Sao.class_(Object, {
|
|
|
|
init: function(el, source, match_selected, format) {
|
2015-10-02 14:32:53 +02:00
|
|
|
if (!el.is('input')) {
|
|
|
|
el.addClass('dropdown');
|
|
|
|
this.dropdown = el;
|
|
|
|
} else {
|
|
|
|
el.wrap('<div class="dropdown"/>');
|
|
|
|
this.dropdown = el.parent();
|
|
|
|
}
|
2015-10-02 11:49:23 +02:00
|
|
|
this.input = el.find('input').add(el.filter('input')).first();
|
2015-10-23 11:41:45 +02:00
|
|
|
this.input.attr('autocomplete', 'off');
|
2015-10-02 11:49:23 +02:00
|
|
|
// bootstrap requires an element with data-toggle
|
|
|
|
jQuery('<span/>', {
|
|
|
|
'data-toggle': 'dropdown'
|
|
|
|
}).appendTo(this.dropdown);
|
|
|
|
this.menu = jQuery('<ul/>', {
|
|
|
|
'class': 'dropdown-menu',
|
|
|
|
'role': 'listbox'
|
|
|
|
}).appendTo(this.dropdown);
|
|
|
|
this.separator = jQuery('<li/>', {
|
|
|
|
'role': 'separator',
|
|
|
|
'class': 'divider'
|
|
|
|
}).appendTo(this.menu);
|
|
|
|
this.separator.hide();
|
|
|
|
|
|
|
|
this.source = source;
|
|
|
|
this.match_selected = match_selected;
|
|
|
|
this.format = format;
|
|
|
|
this.action_activated = null;
|
|
|
|
|
|
|
|
this._search_text = null;
|
|
|
|
|
|
|
|
this.input.on('input', function() {
|
|
|
|
window.setTimeout(this._input.bind(this), 300,
|
|
|
|
this.input.val());
|
|
|
|
}.bind(this));
|
|
|
|
this.input.keydown(function(evt) {
|
2015-10-16 13:46:44 +02:00
|
|
|
if (evt.which == Sao.common.ESC_KEYCODE) {
|
2015-10-02 11:49:23 +02:00
|
|
|
if (this.dropdown.hasClass('open')) {
|
|
|
|
this.menu.dropdown('toggle');
|
|
|
|
}
|
|
|
|
} else if (evt.which == Sao.common.RETURN_KEYCODE) {
|
|
|
|
if (!this.dropdown.hasClass('open')) {
|
|
|
|
this.menu.dropdown('toggle');
|
|
|
|
}
|
2015-04-30 17:38:56 +02:00
|
|
|
}
|
2015-10-02 11:49:23 +02:00
|
|
|
}.bind(this));
|
2015-10-16 13:49:19 +02:00
|
|
|
// We must set the overflow of the treeview containing the input to
|
|
|
|
// visible to prevent vertical scrollbar inherited from the auto
|
|
|
|
// overflow-x
|
|
|
|
// (see http://www.w3.org/TR/css-overflow-3/#overflow-properties)
|
|
|
|
this.dropdown.on('hide.bs.dropdown', function() {
|
2015-10-02 11:49:23 +02:00
|
|
|
this.input.focus();
|
2015-10-16 13:49:19 +02:00
|
|
|
this.input.closest('.treeview').css('overflow', '');
|
|
|
|
}.bind(this));
|
|
|
|
this.dropdown.on('show.bs.dropdown', function() {
|
|
|
|
this.input.closest('.treeview').css('overflow', 'visible');
|
2015-10-02 11:49:23 +02:00
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
set_actions: function(actions, action_activated) {
|
|
|
|
if (action_activated !== undefined) {
|
|
|
|
this.action_activated = action_activated;
|
2015-04-30 17:38:56 +02:00
|
|
|
}
|
2015-10-02 11:49:23 +02:00
|
|
|
this.menu.find('li.action').remove();
|
|
|
|
if (jQuery.isEmptyObject(actions)) {
|
|
|
|
this.separator.hide();
|
|
|
|
return;
|
2015-03-13 16:45:47 +01:00
|
|
|
}
|
2015-10-02 11:49:23 +02:00
|
|
|
this.separator.show();
|
|
|
|
actions.forEach(function(action) {
|
|
|
|
var action_id = action[0];
|
|
|
|
var content = action[1];
|
|
|
|
jQuery('<li/>', {
|
|
|
|
'class': 'action action-' + action_id
|
|
|
|
}).append(jQuery('<a/>', {
|
|
|
|
'href': '#'
|
|
|
|
}).append(this._format_action(content)))
|
|
|
|
.click(function() {
|
|
|
|
if (this.action_activated) {
|
|
|
|
this.action_activated(action_id);
|
|
|
|
}
|
|
|
|
this.input.val('');
|
|
|
|
}.bind(this))
|
|
|
|
.appendTo(this.menu);
|
|
|
|
}, this);
|
|
|
|
},
|
|
|
|
_format: function(content) {
|
|
|
|
if (this.format) {
|
|
|
|
return this.format(content);
|
2015-03-13 16:45:47 +01:00
|
|
|
}
|
2015-10-02 11:49:23 +02:00
|
|
|
return content;
|
|
|
|
},
|
|
|
|
_format_action: function(content) {
|
|
|
|
if (this.format_action) {
|
|
|
|
return this.format_action(content);
|
2015-03-13 16:45:47 +01:00
|
|
|
}
|
2015-10-02 11:49:23 +02:00
|
|
|
return content;
|
|
|
|
},
|
|
|
|
_input: function(text) {
|
|
|
|
if (text != this.input.val()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var prm;
|
|
|
|
if (this.source instanceof Array) {
|
|
|
|
prm = jQuery.when(source.filter(function(value) {
|
|
|
|
return value.toLowerCase().startsWith(text.toLowerCase());
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
prm = this.source(text);
|
|
|
|
}
|
|
|
|
prm.then(function(values) {
|
|
|
|
if (text != this.input.val()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._set_selection(values);
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
_set_selection: function(values) {
|
|
|
|
if (values === undefined) {
|
|
|
|
values = [];
|
|
|
|
}
|
|
|
|
this.menu.find('li.completion').remove();
|
|
|
|
values.reverse().forEach(function(value) {
|
|
|
|
jQuery('<li/>', {
|
|
|
|
'class': 'completion'
|
|
|
|
}).append(jQuery('<a/>', {
|
|
|
|
'href': '#'
|
|
|
|
}).append(this._format(value)))
|
|
|
|
.click(function() {
|
|
|
|
if (this.match_selected) {
|
|
|
|
this.match_selected(value);
|
|
|
|
}
|
|
|
|
this.input.focus();
|
|
|
|
}.bind(this)).prependTo(this.menu);
|
|
|
|
}, this);
|
|
|
|
if (!this.input.val()) {
|
|
|
|
if (this.dropdown.hasClass('open')) {
|
|
|
|
this.menu.dropdown('toggle');
|
2015-03-13 16:45:47 +01:00
|
|
|
}
|
2015-10-02 11:49:23 +02:00
|
|
|
} else {
|
|
|
|
if (!this.dropdown.hasClass('open')) {
|
|
|
|
this.menu.dropdown('toggle');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Sao.common.get_completion = function(el, source,
|
|
|
|
match_selected, action_activated) {
|
|
|
|
var format = function(content) {
|
|
|
|
return content.rec_name;
|
2015-03-13 16:45:47 +01:00
|
|
|
};
|
2015-10-02 11:49:23 +02:00
|
|
|
var completion = new Sao.common.InputCompletion(
|
|
|
|
el, source, match_selected, format);
|
|
|
|
completion.set_actions([
|
|
|
|
['search', Sao.i18n.gettext('Search...')],
|
2015-11-15 21:20:47 +01:00
|
|
|
['create', Sao.i18n.gettext('Create...')]],
|
2015-10-02 11:49:23 +02:00
|
|
|
action_activated);
|
|
|
|
};
|
|
|
|
|
|
|
|
Sao.common.update_completion = function(
|
|
|
|
entry, record, field, model, domain) {
|
|
|
|
var search_text = entry.val();
|
|
|
|
if (!search_text || !model) {
|
|
|
|
return jQuery.when();
|
|
|
|
}
|
|
|
|
if (domain === undefined) {
|
|
|
|
domain = field.get_domain(record);
|
|
|
|
}
|
|
|
|
var context = field.get_context(record);
|
|
|
|
domain = [['rec_name', 'ilike', '%' + search_text + '%'], domain];
|
2015-03-13 16:45:47 +01:00
|
|
|
|
2015-10-02 11:49:23 +02:00
|
|
|
var sao_model = new Sao.Model(model);
|
|
|
|
return sao_model.execute('search_read',
|
|
|
|
[domain, 0, Sao.config.limit, null, ['rec_name']], context);
|
2015-03-13 16:45:47 +01:00
|
|
|
};
|
2015-10-15 10:33:37 +02:00
|
|
|
|
|
|
|
Sao.common.Paned = Sao.class_(Object, {
|
|
|
|
init: function(orientation) {
|
|
|
|
var row;
|
|
|
|
this._orientation = orientation;
|
2015-10-23 12:38:43 +02:00
|
|
|
this.el = jQuery('<div/>');
|
2015-10-15 10:33:37 +02:00
|
|
|
if (orientation == 'horizontal') {
|
|
|
|
row = jQuery('<div/>', {
|
|
|
|
'class': 'row'
|
|
|
|
}).appendTo(this.el);
|
|
|
|
this.child1 = jQuery('<div/>', {
|
|
|
|
'class': 'col-md-6'
|
|
|
|
}).appendTo(row);
|
|
|
|
this.child2 = jQuery('<div/>', {
|
|
|
|
'class': 'col-md-6'
|
|
|
|
}).appendTo(row);
|
|
|
|
} else if (orientation == 'vertical') {
|
|
|
|
this.child1 = jQuery('<div/>', {
|
|
|
|
'class': 'row'
|
|
|
|
}).appendTo(this.el);
|
|
|
|
this.child2 = jQuery('<div/>', {
|
|
|
|
'class': 'row'
|
|
|
|
}).appendTo(this.el);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
get_child1: function() {
|
|
|
|
return this.child1;
|
|
|
|
},
|
|
|
|
get_child2: function() {
|
|
|
|
return this.child2;
|
|
|
|
}
|
|
|
|
});
|
2015-10-27 10:55:57 +01:00
|
|
|
|
|
|
|
Sao.common.get_focus_chain = function(element) {
|
|
|
|
var elements = element.find('input', 'textarea');
|
|
|
|
elements.sort(function(a, b) {
|
|
|
|
if (('tabindex' in a.attributes) && ('tabindex' in b.attributes)) {
|
|
|
|
var a_tabindex = parseInt(a.attributes.tabindex.value);
|
|
|
|
var b_tabindex = parseInt(b.attributes.tabindex.value);
|
|
|
|
return a_tabindex - b_tabindex;
|
|
|
|
} else if ('tabindex' in a.attributes) {
|
|
|
|
return -1;
|
|
|
|
} else if ('tabindex' in b.attributes) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return elements;
|
|
|
|
};
|
|
|
|
|
|
|
|
Sao.common.find_focusable_child = function(element) {
|
|
|
|
var i, len, children, focusable;
|
|
|
|
|
|
|
|
if (!element.is(':visible')) {
|
|
|
|
return null;
|
|
|
|
}
|
2015-10-27 18:06:55 +01:00
|
|
|
if (~['input', 'select', 'textarea'].indexOf(
|
|
|
|
element[0].tagName.toLowerCase())) {
|
2015-10-27 10:55:57 +01:00
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
|
|
|
children = Sao.common.get_focus_chain(element);
|
|
|
|
for (i = 0, len = children.length; i < len; i++) {
|
|
|
|
focusable = Sao.common.find_focusable_child(jQuery(children[i]));
|
|
|
|
if (focusable) {
|
|
|
|
return focusable;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Sao.common.find_first_focus_widget = function(ancestor, widgets) {
|
|
|
|
var i, j;
|
|
|
|
var children, commons, is_common;
|
|
|
|
|
|
|
|
if (widgets.length == 1) {
|
|
|
|
return jQuery(widgets[0]);
|
|
|
|
}
|
|
|
|
children = Sao.common.get_focus_chain(ancestor);
|
|
|
|
for (i = 0; i < children.length; i++) {
|
|
|
|
commons = [];
|
|
|
|
for (j = 0; j < widgets.length; j++) {
|
|
|
|
is_common = jQuery(widgets[j]).closest(children[i]).length > 0;
|
|
|
|
if (is_common) {
|
|
|
|
commons.push(widgets[j]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (commons.length > 0) {
|
|
|
|
return Sao.common.find_first_focus_widget(jQuery(children[i]),
|
|
|
|
commons);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2013-01-04 21:29:48 +01:00
|
|
|
}());
|