The div is only needed if it receive an input element otherwise the element can receive the dropdown class. This avoid to increase the DOM depth and keep the original display of the element.
3114 lines
117 KiB
JavaScript
3114 lines
117 KiB
JavaScript
/* This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
this repository contains the full copyright notices and license terms. */
|
|
(function() {
|
|
'use strict';
|
|
|
|
Sao.common = {};
|
|
|
|
Sao.common.BACKSPACE_KEYCODE = 8;
|
|
Sao.common.TAB_KEYCODE = 9;
|
|
Sao.common.RETURN_KEYCODE = 13;
|
|
Sao.common.ESC_KEYCODE = 27;
|
|
Sao.common.UP_KEYCODE = 38;
|
|
Sao.common.DOWN_KEYCODE = 40;
|
|
Sao.common.DELETE_KEYCODE = 46;
|
|
Sao.common.F2_KEYCODE = 113;
|
|
Sao.common.F3_KEYCODE = 114;
|
|
|
|
Sao.common.SELECTION_NONE = 1;
|
|
Sao.common.SELECTION_SINGLE = 2;
|
|
Sao.common.SELECTION_MULTIPLE = 3;
|
|
|
|
Sao.common.BIG_IMAGE_SIZE = Math.pow(10, 6);
|
|
|
|
Sao.common.compare = function(arr1, arr2) {
|
|
if (arr1.length != arr2.length) {
|
|
return false;
|
|
}
|
|
for (var i = 0; i < arr1.length; i++) {
|
|
if (arr1[i] instanceof Array && arr2[i] instanceof Array) {
|
|
if (!Sao.common.compare(arr1[i], arr2[i])) {
|
|
return false;
|
|
}
|
|
} else if (arr1[i] != arr2[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|
|
// 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) {
|
|
if (evt.type != 'key_press' || e.keyCode == Sao.common.Return) {
|
|
if (one) {
|
|
jQuery(this).off('click keypress', null, handler);
|
|
}
|
|
return func(evt);
|
|
}
|
|
};
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|
|
Sao.common.selection = function(title, values, alwaysask) {
|
|
if (alwaysask === undefined) {
|
|
alwaysask = false;
|
|
}
|
|
var prm = jQuery.Deferred();
|
|
if (jQuery.isEmptyObject(values)) {
|
|
prm.fail();
|
|
return prm;
|
|
}
|
|
var keys = Object.keys(values).sort();
|
|
if ((keys.length == 1) && (!alwaysask)) {
|
|
var key = keys[0];
|
|
prm.resolve(values[key]);
|
|
return prm;
|
|
}
|
|
var dialog = new Sao.Dialog(
|
|
title || Sao.i18n.gettext('Your selection:'),
|
|
'selection-dialog');
|
|
|
|
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'
|
|
}).append(Sao.i18n.gettext('Cancel')).click(function() {
|
|
dialog.modal.modal('hide');
|
|
prm.fail();
|
|
}).appendTo(dialog.footer);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-primary',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('OK')).click(function() {
|
|
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;
|
|
};
|
|
|
|
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('%', '%%')
|
|
;
|
|
};
|
|
|
|
Sao.common.date_format = function(format) {
|
|
if (jQuery.isEmptyObject(format) && Sao.Session.current_session) {
|
|
var context = Sao.Session.current_session.context;
|
|
if (context.locale && context.locale.date) {
|
|
format = context.locale.date;
|
|
}
|
|
}
|
|
if (format) {
|
|
return Sao.common.moment_format(format);
|
|
}
|
|
return '%Y-%m-%d';
|
|
};
|
|
|
|
Sao.common.format_time = function(format, date) {
|
|
if (!date) {
|
|
return '';
|
|
}
|
|
return date.format(Sao.common.moment_format(format));
|
|
};
|
|
|
|
Sao.common.parse_time = function(format, value) {
|
|
if (jQuery.isEmptyObject(value)) {
|
|
return null;
|
|
}
|
|
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;
|
|
};
|
|
return Sao.Time(getNumber('%H'), getNumber('%M'), getNumber('%S'),
|
|
getNumber('%f'));
|
|
};
|
|
|
|
Sao.common.format_date = function(date_format, date) {
|
|
if (!date) {
|
|
return '';
|
|
}
|
|
return date.format(Sao.common.moment_format(date_format));
|
|
};
|
|
|
|
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;
|
|
}
|
|
return date;
|
|
};
|
|
|
|
Sao.common.format_datetime = function(date_format, time_format, date) {
|
|
if (!date) {
|
|
return '';
|
|
}
|
|
return date.format(
|
|
Sao.common.moment_format(date_format + ' ' + time_format));
|
|
};
|
|
|
|
Sao.common.parse_datetime = function(date_format, time_format, value) {
|
|
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;
|
|
}
|
|
return date;
|
|
};
|
|
|
|
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 =
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.w * 4;
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.Y =
|
|
Sao.common.timedelta.DEFAULT_CONVERTER.d * 365;
|
|
Sao.common.timedelta._get_separator = function() {
|
|
return {
|
|
Y: Sao.i18n.gettext('Y'),
|
|
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')
|
|
};
|
|
};
|
|
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);
|
|
}
|
|
return text;
|
|
};
|
|
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);
|
|
};
|
|
|
|
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();
|
|
|
|
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();
|
|
|
|
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();
|
|
|
|
Sao.common.humanize = function(size) {
|
|
var sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
for (var i =0, len = sizes.length; i < len; i++) {
|
|
if (size < 1000) {
|
|
return size.toPrecision(4) + ' ' + sizes[i];
|
|
}
|
|
size /= 1000;
|
|
}
|
|
};
|
|
|
|
Sao.common.EvalEnvironment = function(parent_, eval_type) {
|
|
if (eval_type === undefined)
|
|
eval_type = 'eval';
|
|
var environment;
|
|
if (eval_type == 'eval') {
|
|
environment = parent_.get_eval();
|
|
} else {
|
|
environment = {};
|
|
for (var key in parent_.model.fields) {
|
|
var field = parent_.model.fields[key];
|
|
environment[key] = field.get_on_change_value(parent_);
|
|
}
|
|
}
|
|
environment.id = parent_.id;
|
|
if (parent_.group.parent)
|
|
Object.defineProperty(environment, '_parent_' +
|
|
parent_.group.parent_name, {
|
|
'enumerable': true,
|
|
'get': function() {
|
|
return Sao.common.EvalEnvironment(parent_.group.parent,
|
|
eval_type);
|
|
}
|
|
});
|
|
environment.get = function(item, default_) {
|
|
if (this.hasOwnProperty(item))
|
|
return this[item];
|
|
return default_;
|
|
};
|
|
|
|
return environment;
|
|
};
|
|
|
|
Sao.common.selection_mixin = {};
|
|
Sao.common.selection_mixin.init = function() {
|
|
this.selection = null;
|
|
this.inactive_selection = [];
|
|
this._last_domain = null;
|
|
this._values2selection = {};
|
|
this._domain_cache = {};
|
|
if (this.nullable_widget === undefined) {
|
|
this.nullable_widget = true;
|
|
}
|
|
};
|
|
Sao.common.selection_mixin.init_selection = function(value, callback) {
|
|
if (!value) {
|
|
value = {};
|
|
(this.attributes.selection_change_with || []).forEach(function(e) {
|
|
value[e] = null;
|
|
});
|
|
}
|
|
var key = JSON.stringify(value);
|
|
var selection = this.attributes.selection || [];
|
|
var prm;
|
|
var prepare_selection = function(selection) {
|
|
selection = jQuery.extend([], selection);
|
|
if (this.attributes.sort === undefined || this.attributes.sort) {
|
|
selection.sort(function(a, b) {
|
|
return a[1].localeCompare(b[1]);
|
|
});
|
|
}
|
|
this.selection = jQuery.extend([], selection);
|
|
if (callback) callback(this.selection);
|
|
};
|
|
if (!(selection instanceof Array) &&
|
|
!(key in this._values2selection)) {
|
|
if (!jQuery.isEmptyObject(this.attributes.selection_change_with)) {
|
|
prm = this.model.execute(selection, [value]);
|
|
} else {
|
|
prm = this.model.execute(selection, []);
|
|
}
|
|
prm = prm.then(prepare_selection.bind(this));
|
|
} else {
|
|
if (key in this._values2selection) {
|
|
selection = this._values2selection.selection;
|
|
}
|
|
prepare_selection.call(this, selection);
|
|
prm = jQuery.when();
|
|
}
|
|
this.inactive_selection = [];
|
|
this._selection_prm = prm;
|
|
};
|
|
Sao.common.selection_mixin.update_selection = function(record, field,
|
|
callback) {
|
|
var _update_selection = function() {
|
|
if (!field) {
|
|
if (callback) {
|
|
callback(this.selection);
|
|
}
|
|
return;
|
|
}
|
|
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;
|
|
}
|
|
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));
|
|
};
|
|
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));
|
|
};
|
|
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',
|
|
'params': [[value], ['rec_name'], {}]
|
|
}, 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));
|
|
};
|
|
|
|
Sao.common.Button = Sao.class_(Object, {
|
|
init: function(attributes) {
|
|
this.attributes = attributes;
|
|
this.el = jQuery('<button/>', {
|
|
'class': 'btn btn-default',
|
|
'type': 'button'
|
|
}).text(attributes.string || '')
|
|
.append(jQuery('<span/>', {
|
|
'class': 'glyphicon',
|
|
'aria-hidden': true
|
|
}));
|
|
this.set_icon(attributes.icon);
|
|
},
|
|
set_icon: function(icon_name) {
|
|
if (!icon_name) {
|
|
return;
|
|
}
|
|
var prm = Sao.common.ICONFACTORY.register_icon(icon_name);
|
|
prm.done(function(url) {
|
|
this.el.children('.glyphicon').css(
|
|
'background-image', 'url("' + url + '")');
|
|
}.bind(this));
|
|
},
|
|
set_state: function(record) {
|
|
var states;
|
|
if (record) {
|
|
states = record.expr_eval(this.attributes.states || {});
|
|
if (record.group.get_readonly() || record.readonly()) {
|
|
states.readonly = true;
|
|
}
|
|
} else {
|
|
states = {};
|
|
}
|
|
if (states.invisible) {
|
|
this.el.hide();
|
|
} else {
|
|
this.el.show();
|
|
}
|
|
this.el.prop('disabled', states.readonly);
|
|
this.set_icon(states.icon || this.attributes.icon);
|
|
if (((this.attributes.type === undefined) ||
|
|
(this.attributes.type === 'class')) && (record)) {
|
|
var parent = record.group.parent;
|
|
while (parent) {
|
|
if (parent.has_changed()) {
|
|
this.el.prop('disabled', false);
|
|
break;
|
|
}
|
|
parent = parent.group.parent;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
Sao.common.udlex = Sao.class_(Object, {
|
|
init: function(instream) {
|
|
|
|
var Stream = Sao.class_(Object, {
|
|
init: function(stream) {
|
|
this.stream = stream.split('');
|
|
this.i = 0;
|
|
},
|
|
read: function(length) {
|
|
if (length === undefined) {
|
|
length = 1;
|
|
}
|
|
if (this.i >= this.stream.length) {
|
|
return null;
|
|
}
|
|
var value = this.stream
|
|
.slice(this.i, this.i + length).join();
|
|
this.i += length;
|
|
return value;
|
|
}
|
|
});
|
|
this.instream = new Stream(instream);
|
|
this.eof = null;
|
|
this.commenters = '';
|
|
this.nowordchars = [':', '>', '<', '=', '!', '"', ';', '(', ')'];
|
|
this.whitespace = ' \t\r\n';
|
|
this.whitespace_split = false;
|
|
this.quotes = '"';
|
|
this.escape = '\\';
|
|
this.escapedquotes = '"';
|
|
this.state = ' ';
|
|
this.pushback = [];
|
|
this.token = '';
|
|
},
|
|
get_token: function() {
|
|
if (this.pushback.length > 0) {
|
|
return this.pushback.shift();
|
|
}
|
|
var raw = this.read_token();
|
|
return raw;
|
|
},
|
|
read_token: function() {
|
|
var quoted = false;
|
|
var escapedstate = ' ';
|
|
while (true) {
|
|
var nextchar = this.instream.read(1);
|
|
if (this.state === null) {
|
|
this.token = ''; // past en of file
|
|
break;
|
|
} else if (this.state == ' ') {
|
|
if (!nextchar) {
|
|
this.state = null; // end of file
|
|
break;
|
|
} else if (this.whitespace.contains(nextchar)) {
|
|
if (this.token || quoted) {
|
|
break; // emit current token
|
|
} else {
|
|
continue;
|
|
}
|
|
} else if (this.commenters.contains(nextchar)) {
|
|
// TODO readline
|
|
} else if (this.escape.contains(nextchar)) {
|
|
escapedstate = 'a';
|
|
this.state = nextchar;
|
|
} else if (!~this.nowordchars.indexOf(nextchar)) {
|
|
this.token = nextchar;
|
|
this.state = 'a';
|
|
} else if (this.quotes.contains(nextchar)) {
|
|
this.state = nextchar;
|
|
} else if (this.whitespace_split) {
|
|
this.token = nextchar;
|
|
this.state = 'a';
|
|
} else {
|
|
this.token = nextchar;
|
|
if (this.token || quoted) {
|
|
break; // emit current token
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
} else if (this.quotes.contains(this.state)) {
|
|
quoted = true;
|
|
if (!nextchar) { // end of file
|
|
throw 'no closing quotation';
|
|
}
|
|
if (nextchar == this.state) {
|
|
this.state = 'a';
|
|
} else if (this.escape.contains(nextchar) &&
|
|
this.escapedquotes.contains(this.state)) {
|
|
escapedstate = this.state;
|
|
this.state = nextchar;
|
|
} else {
|
|
this.token = this.token + nextchar;
|
|
}
|
|
} else if (this.escape.contains(this.state)) {
|
|
if (!nextchar) { // end of file
|
|
throw 'no escaped character';
|
|
}
|
|
if (this.quotes.contains(escapedstate) &&
|
|
(nextchar != this.state) &&
|
|
(nextchar != escapedstate)) {
|
|
this.token = this.token + this.state;
|
|
}
|
|
this.token = this.token + nextchar;
|
|
this.state = escapedstate;
|
|
} else if (this.state == 'a') {
|
|
if (!nextchar) {
|
|
this.state = null; // end of file
|
|
break;
|
|
} else if (this.whitespace.contains(nextchar)) {
|
|
this.state = ' ';
|
|
if (this.token || quoted) {
|
|
break; // emit current token
|
|
} else {
|
|
continue;
|
|
}
|
|
} else if (this.commenters.contains(nextchar)) {
|
|
// TODO
|
|
} else if (this.quotes.contains(nextchar)) {
|
|
this.state = nextchar;
|
|
} else if (this.escape.contains(nextchar)) {
|
|
escapedstate = 'a';
|
|
this.state = nextchar;
|
|
} else if ((!~this.nowordchars.indexOf(nextchar)) ||
|
|
this.quotes.contains(nextchar) ||
|
|
this.whitespace_split) {
|
|
this.token = this.token + nextchar;
|
|
} else {
|
|
this.pushback.unshift(nextchar);
|
|
this.state = ' ';
|
|
if (this.token) {
|
|
break; // emit current token
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var result = this.token;
|
|
this.token = '';
|
|
if (!quoted && result === '') {
|
|
result = null;
|
|
}
|
|
return result;
|
|
},
|
|
next: function() {
|
|
var token = this.get_token();
|
|
if (token == this.eof) {
|
|
return null;
|
|
}
|
|
return token;
|
|
}
|
|
});
|
|
|
|
Sao.common.DomainParser = Sao.class_(Object, {
|
|
OPERATORS: ['!=', '<=', '>=', '=', '!', '<', '>'],
|
|
init: function(fields, context) {
|
|
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;
|
|
}
|
|
}
|
|
this.context = context;
|
|
},
|
|
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;
|
|
}
|
|
},
|
|
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_);
|
|
},
|
|
string: function(domain) {
|
|
|
|
var string = function(clause) {
|
|
if (jQuery.isEmptyObject(clause)) {
|
|
return '';
|
|
}
|
|
if ((typeof clause[0] != 'string') ||
|
|
~['AND', 'OR'].indexOf(clause[0])) {
|
|
return '(' + this.string(clause) + ')';
|
|
}
|
|
var escaped;
|
|
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);
|
|
}
|
|
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 = '=';
|
|
} else {
|
|
operator = '!';
|
|
}
|
|
value = value.replace('%%', '%');
|
|
}
|
|
}
|
|
var def_operator = this.default_operator(field);
|
|
if ((def_operator == operator.trim()) ||
|
|
(operator.contains(def_operator) &&
|
|
(operator.contains('not') ||
|
|
operator.contains('!')))) {
|
|
operator = operator.replace(def_operator, '')
|
|
.replace('not', '!').trim();
|
|
}
|
|
if (operator.endsWith('in')) {
|
|
if (operator == 'not in') {
|
|
operator = '!';
|
|
} else {
|
|
operator = '';
|
|
}
|
|
}
|
|
var formatted_value = this.format_value(field, value, target);
|
|
if (~this.OPERATORS.indexOf(operator) &&
|
|
~['char', 'text', 'selection']
|
|
.indexOf(field.type) &&
|
|
(value === '')) {
|
|
formatted_value = '""';
|
|
}
|
|
return (this.quote(field.string) + ': ' +
|
|
operator + formatted_value);
|
|
};
|
|
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);
|
|
},
|
|
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);
|
|
}
|
|
new_domain = this.append_ending_clause(domain,
|
|
[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()];
|
|
}
|
|
if (operator === null) {
|
|
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 [];
|
|
},
|
|
group_operator: function(tokens) {
|
|
var cur = tokens[0];
|
|
var nex = null;
|
|
var result = [];
|
|
tokens.slice(1).forEach(function(nex) {
|
|
if ((nex == '=') && cur &&
|
|
~this.OPERATORS.indexOf(cur + nex)) {
|
|
result.push(cur + nex);
|
|
cur = null;
|
|
} else {
|
|
if (cur !== null) {
|
|
result.push(cur);
|
|
}
|
|
cur = nex;
|
|
}
|
|
}.bind(this));
|
|
if (cur !== null) {
|
|
result.push(cur);
|
|
}
|
|
return result;
|
|
},
|
|
parenthesize: function(tokens) {
|
|
var result = [];
|
|
var current = result;
|
|
var parent = [];
|
|
tokens.forEach(function(token, i) {
|
|
if (current === undefined) {
|
|
return;
|
|
}
|
|
if (token == '(') {
|
|
parent.push(current);
|
|
current = current[current.push([]) - 1];
|
|
} else if (token == ')') {
|
|
current = parent.pop();
|
|
} else {
|
|
current.push(token);
|
|
}
|
|
});
|
|
return result;
|
|
},
|
|
group: function(tokens) {
|
|
var result = [];
|
|
|
|
var _group = function(parts) {
|
|
var result = [];
|
|
var push_result = function(part) {
|
|
result.push([part]);
|
|
};
|
|
var i = parts.indexOf(':');
|
|
if (!~i) {
|
|
parts.forEach(push_result);
|
|
result.forEach(function (e) {
|
|
e.clause = true;
|
|
});
|
|
return result;
|
|
}
|
|
var sub_group = function(name, lvalue) {
|
|
return function(part) {
|
|
if (!jQuery.isEmptyObject(name)) {
|
|
var clause;
|
|
if (!jQuery.isEmptyObject(lvalue)) {
|
|
if (part[0] !== null) {
|
|
lvalue.push(part[0]);
|
|
}
|
|
clause = name.concat([lvalue]);
|
|
clause.clause = true;
|
|
result.push(clause);
|
|
} else {
|
|
clause = name.concat(part);
|
|
clause.clause = true;
|
|
result.push(clause);
|
|
}
|
|
name.splice(0, name.length);
|
|
} else {
|
|
result.push(part);
|
|
}
|
|
};
|
|
};
|
|
for (var j = 0; j < i; j++) {
|
|
var name = parts.slice(j, i).join(' ');
|
|
if (name.toLowerCase() in this.strings) {
|
|
if (!jQuery.isEmptyObject(parts.slice(0, j))) {
|
|
parts.slice(0, j).forEach(push_result);
|
|
} else {
|
|
push_result(null);
|
|
}
|
|
name = [name];
|
|
if (((i + 1) < parts.length) &&
|
|
(~this.OPERATORS.indexOf(parts[i + 1]))) {
|
|
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)) {
|
|
var clause;
|
|
if (!jQuery.isEmptyObject(lvalue)) {
|
|
clause = name.concat([lvalue]);
|
|
clause.clause = true;
|
|
result.push(clause);
|
|
} else {
|
|
clause = name.concat([null]);
|
|
clause.clause = true;
|
|
result.push(clause);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
_group = _group.bind(this);
|
|
|
|
var parts = [];
|
|
tokens.forEach(function(token) {
|
|
if (this.is_generator(token)) {
|
|
_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;
|
|
},
|
|
is_generator: function(value) {
|
|
return (value instanceof Array) && (value.clause === undefined);
|
|
},
|
|
operatorize: function(tokens, operator) {
|
|
var result = [];
|
|
operator = operator || 'or';
|
|
tokens = jQuery.extend([], tokens);
|
|
var test = function(value) {
|
|
if (value instanceof Array) {
|
|
return Sao.common.compare(value, [operator]);
|
|
} else {
|
|
return value == operator;
|
|
}
|
|
};
|
|
var cur = tokens.shift();
|
|
while (test(cur)) {
|
|
cur = tokens.shift();
|
|
}
|
|
if (cur === undefined) {
|
|
return result;
|
|
}
|
|
if (this.is_generator(cur)) {
|
|
cur = this.operatorize(cur, operator);
|
|
}
|
|
var nex = null;
|
|
while (!jQuery.isEmptyObject(tokens)) {
|
|
nex = tokens.shift();
|
|
if ((this.is_generator(nex)) && !test(nex)) {
|
|
nex = this.operatorize(nex, operator);
|
|
}
|
|
if (test(nex)) {
|
|
nex = tokens.shift();
|
|
while (test(nex)) {
|
|
nex = tokens.shift();
|
|
}
|
|
if (this.is_generator(nex)) {
|
|
nex = this.operatorize(nex, operator);
|
|
}
|
|
if (nex !== undefined) {
|
|
cur = [operator.toUpperCase(), cur, nex];
|
|
} else {
|
|
if (!test(cur)) {
|
|
result.push([operator.toUpperCase(), cur]);
|
|
cur = null;
|
|
}
|
|
}
|
|
nex = null;
|
|
} else {
|
|
if (!test(cur)) {
|
|
result.push(cur);
|
|
}
|
|
cur = nex;
|
|
}
|
|
}
|
|
if (jQuery.isEmptyObject(tokens)) {
|
|
if ((nex !== null) && !test(nex)) {
|
|
result.push(nex);
|
|
} else if ((cur !== null) && !test(cur)) {
|
|
result.push(cur);
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
_clausify: function(e) {
|
|
e.clause = true;
|
|
return e;
|
|
},
|
|
parse_clause: function(tokens) {
|
|
var result = [];
|
|
tokens.forEach(function(clause) {
|
|
if (this.is_generator(clause)) {
|
|
result.concat(this.parse_clause(clause));
|
|
} else if ((clause == 'OR') || (clause == 'AND')) {
|
|
result.push(clause);
|
|
} else if ((clause.length == 1) &&
|
|
!(clause[0] instanceof Array)) {
|
|
result.push(this._clausify(['rec_name', 'ilike',
|
|
this.likify(clause[0])]));
|
|
} else if ((clause.length == 3) &&
|
|
(clause[0].toLowerCase() in this.strings)) {
|
|
var name = clause[0];
|
|
var operator = clause[1];
|
|
var value = clause[2];
|
|
var field = this.strings[clause[0].toLowerCase()];
|
|
|
|
var target = null;
|
|
if (field.type == 'reference') {
|
|
var split = this.split_target_value(field, value);
|
|
target = split[0];
|
|
value = split[1];
|
|
}
|
|
|
|
if (operator === null) {
|
|
operator = this.default_operator(field);
|
|
}
|
|
if (value instanceof Array) {
|
|
if (operator == '!') {
|
|
operator = 'not in';
|
|
} else {
|
|
operator = 'in';
|
|
}
|
|
}
|
|
if (operator == '!') {
|
|
operator = this.negate_operator(
|
|
this.default_operator(field));
|
|
}
|
|
if (~['integer', 'float', 'numeric', 'datetime', 'date',
|
|
'time'].indexOf(field.type)) {
|
|
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([
|
|
this._clausify([field.name, '>=', lvalue]),
|
|
this._clausify([field.name, '<=', rvalue])
|
|
]);
|
|
return;
|
|
}
|
|
}
|
|
if (value instanceof Array) {
|
|
value = value.map(function(v) {
|
|
return this.convert_value(field, v);
|
|
}.bind(this));
|
|
} else {
|
|
value = this.convert_value(field, value);
|
|
}
|
|
if (operator.contains('like')) {
|
|
value = this.likify(value);
|
|
}
|
|
if (target) {
|
|
result.push(this._clausify([field.name + '.rec_name',
|
|
operator, value, target]));
|
|
} else {
|
|
result.push(this._clausify(
|
|
[field.name, operator, value]));
|
|
}
|
|
}
|
|
}.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;
|
|
}
|
|
if (value.contains('\\')) {
|
|
value = value.replace(new RegExp('\\\\', 'g'), '\\\\');
|
|
}
|
|
if (value.contains('"')) {
|
|
value = value.replace(new RegExp('"', 'g'), '\\"');
|
|
}
|
|
var tests = [':', ' ', '(', ')'].concat(this.OPERATORS);
|
|
for (var i = 0; i < tests.length; i++) {
|
|
var test = tests[i];
|
|
if (value.contains(test)) {
|
|
return '"' + value + '"';
|
|
}
|
|
}
|
|
return value;
|
|
},
|
|
default_operator: function(field) {
|
|
if (~['char', 'text', 'many2one', 'many2many', 'one2many',
|
|
'reference'].indexOf(field.type)) {
|
|
return 'ilike';
|
|
} else {
|
|
return '=';
|
|
}
|
|
},
|
|
negate_operator: function(operator) {
|
|
switch (operator) {
|
|
case 'ilike':
|
|
return 'not ilike';
|
|
case '=':
|
|
return '!=';
|
|
case 'in':
|
|
return 'not in';
|
|
}
|
|
},
|
|
time_format: function(field) {
|
|
return new Sao.PYSON.Decoder({}).decode(field.format);
|
|
},
|
|
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];
|
|
},
|
|
convert_value: function(field, value) {
|
|
var convert_selection = function() {
|
|
if (typeof value == 'string') {
|
|
for (var i = 0; i < field.selection.length; i++) {
|
|
var selection = field.selection[i];
|
|
var key = selection[0];
|
|
var text = selection[1];
|
|
if (value.toLowerCase() == text.toLowerCase()) {
|
|
return key;
|
|
}
|
|
}
|
|
}
|
|
return value;
|
|
};
|
|
|
|
var converts = {
|
|
'boolean': function() {
|
|
if (typeof value == 'string') {
|
|
return [Sao.i18n.gettext('y'),
|
|
Sao.i18n.gettext('yes'),
|
|
Sao.i18n.gettext('true'),
|
|
Sao.i18n.gettext('t'),
|
|
'1'].some(
|
|
function(test) {
|
|
return test.toLowerCase().startsWith(
|
|
value.toLowerCase());
|
|
});
|
|
} else {
|
|
return Boolean(value);
|
|
}
|
|
},
|
|
'float': function() {
|
|
var result = Number(value);
|
|
if (isNaN(result) || value === '' || value === null) {
|
|
return null;
|
|
} else {
|
|
return result;
|
|
}
|
|
},
|
|
'integer': function() {
|
|
var result = parseInt(value, 10);
|
|
if (isNaN(result)) {
|
|
return null;
|
|
} else {
|
|
return result;
|
|
}
|
|
},
|
|
'numeric': function() {
|
|
var result = new Sao.Decimal(value);
|
|
if (isNaN(result.valueOf()) ||
|
|
value === '' || value === null) {
|
|
return null;
|
|
} else {
|
|
return result;
|
|
}
|
|
},
|
|
'selection': convert_selection,
|
|
'reference': convert_selection,
|
|
'datetime': function() {
|
|
var result = Sao.common.parse_datetime(
|
|
Sao.common.date_format(),
|
|
this.time_format(field),
|
|
value);
|
|
if (!result) {
|
|
result = Sao.common.parse_date(
|
|
Sao.common.date_format(),
|
|
value);
|
|
}
|
|
return result;
|
|
}.bind(this),
|
|
'date': function() {
|
|
return Sao.common.parse_date(
|
|
Sao.common.date_format(),
|
|
value);
|
|
},
|
|
'time': function() {
|
|
try {
|
|
return Sao.common.parse_time(this.time_format(field),
|
|
value);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}.bind(this),
|
|
'timedelta': function() {
|
|
var converter = null;
|
|
if (field.converter) {
|
|
converter = this.context[field.converter];
|
|
}
|
|
return Sao.common.timedelta.parse(value, converter);
|
|
}.bind(this),
|
|
'many2one': function() {
|
|
if (value === '') {
|
|
return null;
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
};
|
|
var func = converts[field.type];
|
|
if (func) {
|
|
return func();
|
|
} else {
|
|
return value;
|
|
}
|
|
},
|
|
format_value: function(field, value, target) {
|
|
if (target === undefined) {
|
|
target = null;
|
|
}
|
|
var format_float = function() {
|
|
if (!value && value !== 0 && value !== new Sao.Decimal(0)) {
|
|
return '';
|
|
}
|
|
var digit = String(value).split('.')[1];
|
|
if (digit) {
|
|
digit = digit.length;
|
|
} else {
|
|
digit = 0;
|
|
}
|
|
return value.toFixed(digit);
|
|
};
|
|
var format_selection = function() {
|
|
for (var i = 0; i < field.selection.length; i++) {
|
|
if (field.selection[i][0] == value) {
|
|
return field.selection[i][1];
|
|
}
|
|
}
|
|
return value || '';
|
|
};
|
|
|
|
var 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;
|
|
};
|
|
|
|
var converts = {
|
|
'boolean': function() {
|
|
if (value) {
|
|
return Sao.i18n.gettext('True');
|
|
} else {
|
|
return Sao.i18n.gettext('False');
|
|
}
|
|
},
|
|
'integer': function() {
|
|
if (value || value === 0) {
|
|
return '' + parseInt(value, 10);
|
|
} else {
|
|
return '';
|
|
}
|
|
},
|
|
'float': format_float,
|
|
'numeric': format_float,
|
|
'selection': format_selection,
|
|
'reference': format_reference,
|
|
'datetime': function() {
|
|
if (!value) {
|
|
return '';
|
|
}
|
|
if (value.isDate ||
|
|
!(value.hour() ||
|
|
value.minute() ||
|
|
value.second())) {
|
|
return Sao.common.format_date(
|
|
Sao.common.date_format(),
|
|
value);
|
|
}
|
|
return Sao.common.format_datetime(
|
|
Sao.common.date_format(),
|
|
this.time_format(field),
|
|
value);
|
|
}.bind(this),
|
|
'date': function() {
|
|
return Sao.common.format_date(
|
|
Sao.common.date_format(),
|
|
value);
|
|
},
|
|
'time': function() {
|
|
if (!value) {
|
|
return '';
|
|
}
|
|
return Sao.common.format_time(
|
|
this.time_format(field),
|
|
value);
|
|
}.bind(this),
|
|
'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),
|
|
'many2one': function() {
|
|
if (value === null) {
|
|
return '';
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
};
|
|
if (value instanceof Array) {
|
|
return value.map(function(v) {
|
|
return this.format_value(field, v);
|
|
}.bind(this)).join(';');
|
|
} else {
|
|
var func = converts[field.type];
|
|
if (func) {
|
|
return this.quote(func(value));
|
|
} else if (value === null) {
|
|
return '';
|
|
} else {
|
|
return this.quote(value);
|
|
}
|
|
}
|
|
},
|
|
simplify: function(value) {
|
|
if ((value instanceof Array) && !this.is_leaf(value)) {
|
|
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;
|
|
}
|
|
});
|
|
|
|
Sao.common.DomainInversion = Sao.class_(Object, {
|
|
and: function(a, b) {return a && b;},
|
|
or: function(a, b) {return a || b;},
|
|
OPERATORS: {
|
|
'=': function(a, b) {
|
|
if ((a instanceof Array) && (b instanceof Array)) {
|
|
return Sao.common.compare(a, b);
|
|
} else {
|
|
return (a === b);
|
|
}
|
|
},
|
|
'>': function(a, b) {return (a > b);},
|
|
'<': function(a, b) {return (a < b);},
|
|
'<=': function(a, b) {return (a <= b);},
|
|
'>=': function(a, b) {return (a >= b);},
|
|
'!=': function(a, b) {
|
|
if ((a instanceof Array) && (b instanceof Array)) {
|
|
return !Sao.common.compare(a, b);
|
|
} else {
|
|
return (a !== b);
|
|
}
|
|
},
|
|
'in': function(a, b) {
|
|
return Sao.common.DomainInversion.in_(a, b);
|
|
},
|
|
'not in': function(a, b) {
|
|
return !Sao.common.DomainInversion.in_(a, b);
|
|
},
|
|
// Those operators are not supported (yet ?)
|
|
'like': function() {return true;},
|
|
'ilike': function() {return true;},
|
|
'not like': function() {return true;},
|
|
'not ilike': function() {return true;},
|
|
'child_of': function() {return true;},
|
|
'not child_of': function() {return true;}
|
|
},
|
|
locale_part: function(expression, field_name, locale_name) {
|
|
if (locale_name === undefined) {
|
|
locale_name = 'id';
|
|
}
|
|
if (expression === field_name) {
|
|
return locale_name;
|
|
}
|
|
if (expression.contains('.')) {
|
|
return expression.split('.').slice(1).join('.');
|
|
}
|
|
return expression;
|
|
},
|
|
is_leaf: function(expression) {
|
|
return ((expression instanceof Array) &&
|
|
(expression.length > 2) &&
|
|
(typeof expression[1] == 'string'));
|
|
},
|
|
eval_leaf: function(part, context, boolop) {
|
|
if (boolop === undefined) {
|
|
boolop = this.and;
|
|
}
|
|
var field = part[0];
|
|
var operand = part[1];
|
|
var value = part[2];
|
|
if (field.contains('.')) {
|
|
// In the case where the leaf concerns a m2o then having a
|
|
// value in the evaluation context is deemed suffisant
|
|
return Boolean(context[field.split('.')[0]]);
|
|
}
|
|
if ((operand == '=') &&
|
|
(context[field] === null || context[field] === undefined) &&
|
|
(boolop === this.and)) {
|
|
// We should consider that other domain inversion will set a
|
|
// correct value to this field
|
|
return true;
|
|
}
|
|
var context_field = context[field];
|
|
if ((context_field && context_field._isAMomentObject) && !value) {
|
|
if (context_field.isDateTime) {
|
|
value = Sao.DateTime.min;
|
|
} else {
|
|
value = Sao.Date.min;
|
|
}
|
|
}
|
|
if ((value && value._isAMomentObject) && !context_field) {
|
|
if (value.isDateTime) {
|
|
context_field = Sao.DateTime.min;
|
|
} else {
|
|
context_field = Sao.Date.min;
|
|
}
|
|
}
|
|
if ((typeof context_field == 'string') &&
|
|
(value instanceof Array) && value.length == 2) {
|
|
value = value.join(',');
|
|
} else if ((context_field instanceof Array) &&
|
|
(typeof value == 'string') && context_field.length == 2) {
|
|
context_field = context_field.join(',');
|
|
}
|
|
if (~['=', '!='].indexOf(operand) &&
|
|
context_field instanceof Array &&
|
|
typeof value == 'number') {
|
|
operand = {
|
|
'=': 'in',
|
|
'!=': 'not in'
|
|
}[operand];
|
|
}
|
|
if (operand in this.OPERATORS) {
|
|
return this.OPERATORS[operand](context_field, value);
|
|
} else {
|
|
return true;
|
|
}
|
|
},
|
|
inverse_leaf: function(domain) {
|
|
if (~['AND', 'OR'].indexOf(domain)) {
|
|
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));
|
|
}
|
|
},
|
|
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));
|
|
}
|
|
},
|
|
eval_domain: function(domain, context, boolop) {
|
|
if (boolop === undefined) {
|
|
boolop = this.and;
|
|
}
|
|
if (this.is_leaf(domain)) {
|
|
return this.eval_leaf(domain, context, boolop);
|
|
} else if (jQuery.isEmptyObject(domain) && boolop == this.and) {
|
|
return true;
|
|
} else if (jQuery.isEmptyObject(domain) && boolop == this.or) {
|
|
return false;
|
|
} else if (domain[0] == 'AND') {
|
|
return this.eval_domain(domain.slice(1), context);
|
|
} else if (domain[0] == 'OR') {
|
|
return this.eval_domain(domain.slice(1), context, this.or);
|
|
} else {
|
|
return boolop(this.eval_domain(domain[0], context),
|
|
this.eval_domain(domain.slice(1), context, boolop));
|
|
}
|
|
},
|
|
localize_domain: function(domain, field_name, strip_target) {
|
|
if (~['AND', 'OR', true, false].indexOf(domain)) {
|
|
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));
|
|
}
|
|
}
|
|
var local_name = 'id';
|
|
if (typeof domain[2] == 'string') {
|
|
local_name = 'rec_name';
|
|
}
|
|
var n = strip_target ? 3 : 4;
|
|
return [this.locale_part(domain[0], field_name, local_name)]
|
|
.concat(domain.slice(1, n)).concat(domain.slice(4));
|
|
} else {
|
|
return domain.map(function(e) {
|
|
return this.localize_domain(e, field_name, strip_target);
|
|
}.bind(this));
|
|
}
|
|
},
|
|
simplify: function(domain) {
|
|
if (this.is_leaf(domain)) {
|
|
return domain;
|
|
} else if (~['OR', 'AND'].indexOf(domain)) {
|
|
return domain;
|
|
} else if ((domain instanceof Array) && (domain.length == 1) &&
|
|
(~['OR', 'AND'].indexOf(domain[0]))) {
|
|
return [];
|
|
} else if ((domain instanceof Array) && (domain.length == 1) &&
|
|
(!this.is_leaf(domain[0]))) {
|
|
return this.simplify(domain[0]);
|
|
} else if ((domain instanceof Array) && (domain.length == 2) &&
|
|
~['AND', 'OR'].indexOf(domain[0])) {
|
|
return [this.simplify(domain[1])];
|
|
} else {
|
|
return domain.map(this.simplify.bind(this));
|
|
}
|
|
},
|
|
merge: function(domain, domoperator) {
|
|
if (jQuery.isEmptyObject(domain) ||
|
|
~['AND', 'OR'].indexOf(domain)) {
|
|
return [];
|
|
}
|
|
var domain_type = domain[0] == 'OR' ? 'OR' : 'AND';
|
|
if (this.is_leaf(domain)) {
|
|
return [domain];
|
|
} else if (domoperator === undefined) {
|
|
return [domain_type].concat([].concat.apply([],
|
|
domain.map(function(e) {
|
|
return this.merge(e, domain_type);
|
|
}.bind(this))));
|
|
} else if (domain_type == domoperator) {
|
|
return [].concat.apply([], domain.map(function(e) {
|
|
return this.merge(e, domain_type);
|
|
}.bind(this)));
|
|
} else {
|
|
// without setting the domoperator
|
|
return [this.merge(domain)];
|
|
}
|
|
},
|
|
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));
|
|
},
|
|
unique_value: function(domain) {
|
|
if ((domain instanceof Array) &&
|
|
(domain.length == 1) &&
|
|
!domain[0][0].contains('.') &&
|
|
(domain[0][1] == '=')) {
|
|
return [true, domain[0][1], domain[0][2]];
|
|
} else {
|
|
return [false, null, null];
|
|
}
|
|
},
|
|
parse: function(domain) {
|
|
var And = Sao.common.DomainInversion.And;
|
|
var Or = Sao.common.DomainInversion.Or;
|
|
if (this.is_leaf(domain)) {
|
|
return domain;
|
|
} else if (jQuery.isEmptyObject(domain)) {
|
|
return new And([]);
|
|
} else if (domain[0] === 'OR') {
|
|
return new Or(domain.slice(1));
|
|
} else {
|
|
var begin = 0;
|
|
if (domain[0] === 'AND') {
|
|
begin = 1;
|
|
}
|
|
return new And(domain.slice(begin));
|
|
}
|
|
},
|
|
domain_inversion: function(domain, symbol, context) {
|
|
if (context === undefined) {
|
|
context = {};
|
|
}
|
|
var expression = this.parse(domain);
|
|
if (!~expression.variables.indexOf(symbol)) {
|
|
return true;
|
|
}
|
|
return expression.inverse(symbol, context);
|
|
}
|
|
});
|
|
Sao.common.DomainInversion.in_ = function(a, b) {
|
|
if (a instanceof Array) {
|
|
if (b instanceof Array) {
|
|
for (var i = 0, len = a.length; i < len; i++) {
|
|
if (~b.indexOf(a[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
} else {
|
|
return Boolean(~a.indexOf(b));
|
|
}
|
|
} else {
|
|
return Boolean(~b.indexOf(a));
|
|
}
|
|
};
|
|
Sao.common.DomainInversion.And = Sao.class_(Object, {
|
|
init: function(expressions) {
|
|
this.domain_inversion = new Sao.common.DomainInversion();
|
|
this.branches = expressions.map(this.domain_inversion.parse.bind(
|
|
this.domain_inversion));
|
|
this.variables = [];
|
|
for (var i = 0, len = this.branches.length; i < len; i++) {
|
|
var expression = this.branches[i];
|
|
if (this.domain_inversion.is_leaf(expression)) {
|
|
this.variables.push(this.base(expression[0]));
|
|
} else if (expression instanceof
|
|
Sao.common.DomainInversion.And) {
|
|
this.variables = this.variables.concat(
|
|
expression.variables);
|
|
}
|
|
}
|
|
},
|
|
base: function(expression) {
|
|
if (!expression.contains('.')) {
|
|
return expression;
|
|
} else {
|
|
return expression.split('.')[0];
|
|
}
|
|
},
|
|
inverse: function(symbol, context) {
|
|
var DomainInversion = Sao.common.DomainInversion;
|
|
var result = [];
|
|
for (var i = 0, len = this.branches.length; i < len; i++) {
|
|
var part = this.branches[i];
|
|
if (part instanceof DomainInversion.And) {
|
|
var part_inversion = part.inverse(symbol, context);
|
|
var evaluated = typeof part_inversion == 'boolean';
|
|
if (!evaluated) {
|
|
result.push(part_inversion);
|
|
} else if (part_inversion) {
|
|
continue;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else if (this.domain_inversion.is_leaf(part) &&
|
|
(this.base(part[0]) === symbol)) {
|
|
result.push(part);
|
|
} else {
|
|
var field = part[0];
|
|
if ((!(field in context)) ||
|
|
((field in context) &&
|
|
this.domain_inversion.eval_leaf(part, context,
|
|
this.domain_inversion.and))) {
|
|
result.push(true);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
result = result.filter(function(e) {
|
|
return e !== true;
|
|
});
|
|
if (jQuery.isEmptyObject(result)) {
|
|
return true;
|
|
} else {
|
|
return this.domain_inversion.simplify(result);
|
|
}
|
|
}
|
|
});
|
|
Sao.common.DomainInversion.Or = Sao.class_(Sao.common.DomainInversion.And, {
|
|
inverse: function(symbol, context) {
|
|
var DomainInversion = Sao.common.DomainInversion;
|
|
var result = [];
|
|
if (!~this.variables.indexOf(symbol) &&
|
|
!jQuery.isEmptyObject(this.variables.filter(function(e) {
|
|
return !(e in context);
|
|
}))) {
|
|
// In this case we don't know anything about this OR part, we
|
|
// consider it to be True (because people will have the
|
|
// constraint on this part later).
|
|
return true;
|
|
}
|
|
for (var i = 0, len = this.branches.length; i < len; i++) {
|
|
var part = this.branches[i];
|
|
if (part instanceof DomainInversion.And) {
|
|
var part_inversion = part.inverse(symbol, context);
|
|
var evaluated = typeof part_inversion == 'boolean';
|
|
if (!~this.variables.indexOf(symbol)) {
|
|
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));
|
|
}
|
|
}
|
|
});
|
|
|
|
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';
|
|
}
|
|
};
|
|
|
|
Sao.common.LOCAL_ICONS = [
|
|
'tryton-attachment-hi',
|
|
'tryton-attachment',
|
|
'tryton-bookmark',
|
|
'tryton-cancel',
|
|
'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',
|
|
'tryton-ok',
|
|
'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: [],
|
|
register_prm: jQuery.when(),
|
|
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');
|
|
return icon_model.execute('list_icons', [], {})
|
|
.then(function(icons) {
|
|
if (!refresh) {
|
|
this.name2id = {};
|
|
this.loaded_icons = {};
|
|
}
|
|
this.tryton_icons = [];
|
|
|
|
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));
|
|
}
|
|
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;
|
|
}
|
|
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');
|
|
this.register_prm = loaded_prm.then(function () {
|
|
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',
|
|
[ids, ['name', 'icon']], {});
|
|
return read_prm.then(function(icons) {
|
|
icons.forEach(function(icon) {
|
|
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);
|
|
}
|
|
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));
|
|
return this.register_prm;
|
|
},
|
|
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();
|
|
|
|
Sao.common.UniqueDialog = Sao.class_(Object, {
|
|
init: function() {
|
|
this.running = false;
|
|
},
|
|
build_dialog: function() {
|
|
var dialog = new Sao.Dialog('', this.class_);
|
|
return dialog;
|
|
},
|
|
run: function() {
|
|
if (this.running) {
|
|
return;
|
|
}
|
|
var args = Array.prototype.slice.call(arguments);
|
|
var prm = jQuery.Deferred();
|
|
args.push(prm);
|
|
var dialog = this.build_dialog.apply(this, args);
|
|
this.running = true;
|
|
dialog.modal.modal('show');
|
|
return prm;
|
|
},
|
|
close: function(dialog) {
|
|
dialog.modal.on('hidden.bs.modal', function(event) {
|
|
jQuery(this).remove();
|
|
});
|
|
dialog.modal.modal('hide');
|
|
this.running = false;
|
|
}
|
|
});
|
|
|
|
Sao.common.MessageDialog = Sao.class_(Sao.common.UniqueDialog, {
|
|
class_: 'message-dialog',
|
|
build_dialog: function(message, icon, prm) {
|
|
var dialog = Sao.common.MessageDialog._super.build_dialog.call(
|
|
this);
|
|
dialog.header.remove();
|
|
dialog.body.append(jQuery('<div/>', {
|
|
'class': 'alert alert-info',
|
|
role: 'alert'
|
|
}).append(jQuery('<span/>', {
|
|
'class': 'glyphicon ' + icon,
|
|
'aria-hidden': true
|
|
})).append(jQuery('<span/>', {
|
|
'class': 'sr-only'
|
|
}).append(Sao.i18n.gettext('Message: '))
|
|
).append(jQuery('<span/>')
|
|
.append(message)
|
|
.css('white-space', 'pre-wrap')));
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-primary',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('OK')).click(function() {
|
|
this.close(dialog);
|
|
prm.resolve('ok');
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
return dialog;
|
|
},
|
|
run: function(message, icon) {
|
|
return Sao.common.MessageDialog._super.run.call(
|
|
this, message, icon || 'glyphicon-info-sign');
|
|
}
|
|
});
|
|
Sao.common.message = new Sao.common.MessageDialog();
|
|
|
|
Sao.common.WarningDialog = Sao.class_(Sao.common.UniqueDialog, {
|
|
class_: 'warning-dialog',
|
|
build_dialog: function(message, title, prm) {
|
|
var dialog = Sao.common.WarningDialog._super.build_dialog.call(
|
|
this);
|
|
dialog.add_title(title);
|
|
dialog.body.append(jQuery('<div/>', {
|
|
'class': 'alert alert-warning',
|
|
role: 'alert'
|
|
}).append(jQuery('<span/>', {
|
|
'class': 'glyphicon glyphicon-alert',
|
|
'aria-hidden': true
|
|
})).append(jQuery('<span/>', {
|
|
'class': 'sr-only'
|
|
}).append(Sao.i18n.gettext('Warning: '))
|
|
).append(jQuery('<span/>')
|
|
.append(message)
|
|
.css('white-space', 'pre-wrap')));
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-primary',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('OK')).click(function() {
|
|
this.close(dialog);
|
|
prm.resolve('ok');
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
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);
|
|
dialog.body.append(jQuery('<div/>')
|
|
.append(jQuery('<input/>', {
|
|
'type': 'checkbox'
|
|
}).change(this._set_always.bind(this)))
|
|
.append(jQuery('<span/>')
|
|
.text(Sao.i18n.gettext('Always ignore this warning.')))
|
|
);
|
|
dialog.body.append(jQuery('<p/>')
|
|
.text(Sao.i18n.gettext('Do you want to proceed?')));
|
|
dialog.footer.children().remove();
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-link',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('No')).click(function() {
|
|
this.close(dialog);
|
|
prm.reject();
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-primary',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('Yes')).click(function() {
|
|
this.close(dialog);
|
|
if (this.always) {
|
|
prm.resolve('always');
|
|
}
|
|
prm.resolve('ok');
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
return dialog;
|
|
}
|
|
});
|
|
Sao.common.userwarning = new Sao.common.UserWarningDialog();
|
|
|
|
Sao.common.ConfirmationDialog = Sao.class_(Sao.common.UniqueDialog, {
|
|
class_: 'confirmation-dialog',
|
|
build_dialog: function(message) {
|
|
var dialog = Sao.common.ConfirmationDialog._super.build_dialog.call(
|
|
this);
|
|
dialog.header.remove();
|
|
dialog.body.append(jQuery('<div/>', {
|
|
'class': 'alert alert-info',
|
|
role: 'alert'
|
|
}).append(jQuery('<span/>', {
|
|
'class': 'glyphicon glyphicon-info-sign',
|
|
'aria-hidden': true
|
|
})).append(jQuery('<span/>', {
|
|
'class': 'sr-only'
|
|
}).append(Sao.i18n.gettext('Confirmation: '))
|
|
).append(jQuery('<span/>')
|
|
.append(message)
|
|
.css('white-space', 'pre-wrap')));
|
|
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);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-link',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('Cancel')).click(function() {
|
|
this.close(dialog);
|
|
prm.reject();
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-primary',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('OK')).click(function() {
|
|
this.close(dialog);
|
|
prm.resolve();
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
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);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-link',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('Cancel')).click(function() {
|
|
this.close(dialog);
|
|
prm.resolve('cancel');
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-default',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('No')).click(function() {
|
|
this.close(dialog);
|
|
prm.resolve('ko');
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-primary',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('Yes')).click(function() {
|
|
this.close(dialog);
|
|
prm.resolve('ok');
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
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) {
|
|
var dialog = Sao.common.AskDialog._super.build_dialog.call(this);
|
|
dialog.header.remove();
|
|
var entry = jQuery('<input/>', {
|
|
'class': 'form-control',
|
|
'type': visibility ? 'input' : 'password',
|
|
'id': 'ask-dialog-entry'
|
|
});
|
|
var body = dialog.find('.modal-body');
|
|
body.append(jQuery('<div/>', {
|
|
'class': 'form-group'
|
|
}).append(jQuery('<label/>', {
|
|
'for': 'ask-dialog-entry'
|
|
}).append(question)).append(entry));
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-link',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('Cancel')).click(function() {
|
|
this.close(dialog);
|
|
prm.reject();
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-primary',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('OK')).click(function() {
|
|
this.close(dialog);
|
|
prm.resolve(entry.val());
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
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) {
|
|
var dialog = Sao.common.ConcurrencyDialog._super.build_dialog.call(
|
|
this);
|
|
dialog.modal.find('.modal-dialog'
|
|
).removeClass('modal-sm').addClass('modal-lg');
|
|
dialog.add_title(Sao.i18n.gettext('Concurrency Exception'));
|
|
dialog.body.append(jQuery('<div/>', {
|
|
'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'
|
|
}).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:')))
|
|
.append(jQuery('<ul/>')
|
|
.append(jQuery('<li/>')
|
|
.text(Sao.i18n.gettext('"Cancel" to cancel saving;')))
|
|
.append(jQuery('<li/>')
|
|
.text(Sao.i18n.gettext(
|
|
'"Compare" to see the modified version;')))
|
|
.append(jQuery('<li/>')
|
|
.text(Sao.i18n.gettext(
|
|
'"Write Anyway" to save your current version.'))))
|
|
);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-link',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('Cancel')).click(function() {
|
|
this.close(dialog);
|
|
prm.reject();
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-default',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('Compare')).click(function() {
|
|
this.close(dialog);
|
|
Sao.Tab.create({
|
|
'model': model,
|
|
'res_id': record_id,
|
|
'domain': [['id', '=', record_id]],
|
|
'context': context,
|
|
'mode': ['form', 'tree']
|
|
});
|
|
prm.reject();
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-default',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('Write Anyway')).click(function() {
|
|
this.close(dialog);
|
|
prm.resolve();
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
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) {
|
|
var dialog = Sao.common.ConcurrencyDialog._super.build_dialog.call(
|
|
this);
|
|
dialog.modal.find('.modal-dialog'
|
|
).removeClass('modal-sm').addClass('modal-lg');
|
|
dialog.add_title(Sao.i18n.gettext('Application Error'));
|
|
dialog.body.append(jQuery('<div/>', {
|
|
'class': 'alert alert-warning',
|
|
role: 'alert'
|
|
}).append(jQuery('<span/>', {
|
|
'class': 'glyphicon glyphicon-alert',
|
|
'aria-hidden': true
|
|
})).append(jQuery('<span/>', {
|
|
'class': 'sr-only'
|
|
}).append(Sao.i18n.gettext('Warning: '))
|
|
).append(jQuery('<p/>')
|
|
.append(jQuery('<pre/>')
|
|
.text(details)))
|
|
.append(jQuery('<p/>')
|
|
.append(jQuery('<a/>', {
|
|
'class': 'btn btn-link',
|
|
href: Sao.config.roundup.url,
|
|
target: '_blank'
|
|
}).text(Sao.i18n.gettext('Report Bug')))));
|
|
jQuery('<button/>', {
|
|
'class': 'btn btn-primary',
|
|
'type': 'button'
|
|
}).append(Sao.i18n.gettext('Close')).click(function() {
|
|
this.close(dialog);
|
|
prm.resolve();
|
|
}.bind(this)).appendTo(dialog.footer);
|
|
return dialog;
|
|
}
|
|
});
|
|
Sao.common.error = new Sao.common.ErrorDialog();
|
|
|
|
Sao.common.Processing = Sao.class_(Object, {
|
|
queries: 0,
|
|
timeout: 500,
|
|
init: function() {
|
|
this.el = jQuery('<div/>', {
|
|
'id': 'processing',
|
|
'class': 'text-center'
|
|
});
|
|
this.el.append(jQuery('<span/>', {
|
|
'class': 'label label-info',
|
|
'text': 'Processing...'
|
|
}));
|
|
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;
|
|
this.el.hide();
|
|
}
|
|
}
|
|
});
|
|
Sao.common.processing = new Sao.common.Processing();
|
|
|
|
Sao.common.InputCompletion = Sao.class_(Object, {
|
|
init: function(el, source, match_selected, format) {
|
|
if (!el.is('input')) {
|
|
el.addClass('dropdown');
|
|
this.dropdown = el;
|
|
} else {
|
|
el.wrap('<div class="dropdown"/>');
|
|
this.dropdown = el.parent();
|
|
}
|
|
this.input = el.find('input').add(el.filter('input')).first();
|
|
// 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) {
|
|
if (evt.which == Sao.common.DOWN_KEYCODE) {
|
|
this.menu.find('a').first().focus();
|
|
evt.preventDefault();
|
|
} else if (evt.which == Sao.common.ESC_KEYCODE) {
|
|
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');
|
|
}
|
|
}
|
|
}.bind(this));
|
|
this.menu.on('hide.bs.dropdown', function() {
|
|
this.input.focus();
|
|
}.bind(this));
|
|
},
|
|
set_actions: function(actions, action_activated) {
|
|
if (action_activated !== undefined) {
|
|
this.action_activated = action_activated;
|
|
}
|
|
this.menu.find('li.action').remove();
|
|
if (jQuery.isEmptyObject(actions)) {
|
|
this.separator.hide();
|
|
return;
|
|
}
|
|
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);
|
|
}
|
|
return content;
|
|
},
|
|
_format_action: function(content) {
|
|
if (this.format_action) {
|
|
return this.format_action(content);
|
|
}
|
|
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);
|
|
this.menu.find('a').first().keyup(function(evt) {
|
|
if (evt.which == Sao.common.UP_KEYCODE) {
|
|
this.input.focus();
|
|
}
|
|
}.bind(this));
|
|
if (!this.input.val()) {
|
|
if (this.dropdown.hasClass('open')) {
|
|
this.menu.dropdown('toggle');
|
|
}
|
|
} 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;
|
|
};
|
|
var completion = new Sao.common.InputCompletion(
|
|
el, source, match_selected, format);
|
|
completion.set_actions([
|
|
['search', Sao.i18n.gettext('Search...')],
|
|
['create', Sao.i18n.gettext('Create..')]],
|
|
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];
|
|
|
|
var sao_model = new Sao.Model(model);
|
|
return sao_model.execute('search_read',
|
|
[domain, 0, Sao.config.limit, null, ['rec_name']], context);
|
|
};
|
|
}());
|