From 0dba5d648f9456c874d70f5847ed8e6f53fbb2c3 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 11 Mar 2015 18:00:21 +0100 Subject: [PATCH 01/46] [ADD] web_widget_x2many_2d_matrix --- web_widget_x2many_2d_matrix/README.rst | 56 ++++ web_widget_x2many_2d_matrix/__init__.py | 20 ++ web_widget_x2many_2d_matrix/__openerp__.py | 45 +++ .../static/description/icon.png | Bin 0 -> 1142 bytes .../src/css/web_widget_x2many_2d_matrix.css | 0 .../src/js/web_widget_x2many_2d_matrix.js | 305 ++++++++++++++++++ .../src/xml/web_widget_x2many_2d_matrix.xml | 33 ++ .../views/templates.xml | 11 + 8 files changed, 470 insertions(+) create mode 100644 web_widget_x2many_2d_matrix/README.rst create mode 100644 web_widget_x2many_2d_matrix/__init__.py create mode 100644 web_widget_x2many_2d_matrix/__openerp__.py create mode 100644 web_widget_x2many_2d_matrix/static/description/icon.png create mode 100644 web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css create mode 100644 web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js create mode 100644 web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml create mode 100644 web_widget_x2many_2d_matrix/views/templates.xml diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst new file mode 100644 index 00000000..27a266b9 --- /dev/null +++ b/web_widget_x2many_2d_matrix/README.rst @@ -0,0 +1,56 @@ +2D matrix for x2many fields +=========================== + +This module allows to show an x2many field with 3-tuples +($x_value, $y_value, $value) in a table + ++-----------+-----------+-----------+ +| | $x_value1 | $x_value2 | ++===========+===========+===========+ +| $y_value1 | $value1/1 | $value2/1 | ++-----------+-----------+-----------+ +| $y_value2 | $value1/2 | $value2/2 | ++-----------+-----------+-----------+ + +where `valuen/n` is editable. + + +Usage +===== + +Use this widget by saying:: + + + +This assumes that my_field refers to a model with the fields `x`, `y` and +`value`. If your fields are named differently, pass the correct names as +attributes:: + + + +Known issues / Roadmap +====================== + +* ... + +Credits +======= + +Contributors +------------ + +* Holger Brunn + +Maintainer +---------- + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/web_widget_x2many_2d_matrix/__init__.py b/web_widget_x2many_2d_matrix/__init__.py new file mode 100644 index 00000000..faef9dac --- /dev/null +++ b/web_widget_x2many_2d_matrix/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV . +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py new file mode 100644 index 00000000..1cbc4aad --- /dev/null +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV . +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +{ + "name": "2D matrix for x2many fields", + "version": "1.0", + "author": "Therp BV", + "license": "AGPL-3", + "category": "Hidden/Dependency", + "summary": "Show list fields as a matrix", + "depends": [ + 'web', + ], + "data": [ + 'views/templates.xml', + ], + "qweb": [ + 'static/src/xml/web_widget_x2many_2d_matrix.xml', + ], + "test": [ + ], + "auto_install": False, + "installable": True, + "application": False, + "external_dependencies": { + 'python': [], + }, +} diff --git a/web_widget_x2many_2d_matrix/static/description/icon.png b/web_widget_x2many_2d_matrix/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4c7ab302908e114888446d84d3493fa726033c1f GIT binary patch literal 1142 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpuXS$ zpAc7|0+Vy!%`Sd3J@*~Rz=H@Xz@vBMNCCrhU++~OAXQKjgnPb5^?zLm6yRy4l)f7mx|d; zx?*(k%?4)UEmyi0Ecrc2=k&YFn|8nX@qd4)(saLN%zo##oL4V9SpH%8W(I{5_Kby- zneS~VhopAyb6lBS&$U5E`gKppbdV7NQIC+SE zKe2ts8LoR*Hw$jqcIEDHSU_mi4;HoUDLTgOJMIHx zO|`@|q9i4;B-JXpC>2OC7#SEE>l#?<8d`)H8e5qfSQ#5>8yHy`7. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +//############################################################################ + +openerp.web_widget_x2many_2d_matrix = function(instance) +{ + instance.web.form.widgets.add( + 'x2many_2d_matrix', + 'instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix'); + instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix = instance.web.form.FieldOne2Many.extend({ + template: 'FieldX2Many2dMatrix', + widget_class: 'oe_form_field_x2many_2d_matrix', + + // those will be filled with rows from the dataset + by_x_axis: {}, + by_y_axis: {}, + field_x_axis: 'x', + field_label_x_axis: 'x', + field_y_axis: 'y', + field_label_y_axis: 'y', + field_value: 'value', + // information about our datatype + is_numeric: false, + show_row_totals: true, + show_column_totals: true, + // this will be filled with the model's fields_get + fields: {}, + + // read parameters + init: function(field_manager, node) + { + this.field_x_axis = node.attrs.field_x_axis || this.field_x_axis; + this.field_y_axis = node.attrs.field_y_axis || this.field_y_axis; + this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis; + this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis; + this.field_value = node.attrs.field_value || this.field_value; + this.show_row_totals = node.attrs.show_row_totals || this.show_row_totals; + this.show_column_totals = node.attrs.show_column_totals || this.show_column_totals; + return this._super.apply(this, arguments); + }, + + // return a field's value, id in case it's a one2many field + get_field_value: function(row, field, many2one_as_name) + { + if(this.fields[field].type == 'many2one' && _.isArray(row[field])) + { + if(many2one_as_name) + { + return row[field][1]; + } + else + { + return row[field][0]; + } + } + return row[field]; + }, + + // setup our datastructure for simple access in the template + set_value: function() + { + var self = this, + result = this._super.apply(this, arguments); + + self.by_x_axis = {}; + self.by_y_axis = {}; + + return jQuery.when(result).then(function() + { + return self.dataset._model.call('fields_get').then(function(fields) + { + self.fields = fields; + self.is_numeric = fields[self.field_value].type == 'float'; + self.show_row_totals &= self.is_numeric; + self.show_column_totals &= self.is_numeric; + }).then(function() + { + return self.dataset.read_ids(self.dataset.ids).then(function(rows) + { + var read_many2one = {}, + many2one_fields = [ + self.field_x_axis, self.field_y_axis, + self.field_label_x_axis, self.field_label_y_axis + ]; + // prepare to read many2one names if necessary (we can get (id, name) or just id as value) + _.each(many2one_fields, function(field) + { + if(self.fields[field].type == 'many2one') + { + read_many2one[field] = {}; + } + }); + // setup data structure + _.each(rows, function(row) + { + var x = self.get_field_value(row, self.field_x_axis), + y = self.get_field_value(row, self.field_y_axis); + self.by_x_axis[x] = self.by_x_axis[x] || {}; + self.by_y_axis[y] = self.by_y_axis[y] || {}; + self.by_x_axis[x][y] = row; + self.by_y_axis[y][x] = row; + _.each(read_many2one, function(rows, field) + { + if(!_.isArray(row[field])) + { + rows[row[field]] = rows[row[field]] || [] + rows[row[field]].push(row); + } + }); + }); + // read many2one fields if necessary + var deferrends = []; + _.each(read_many2one, function(rows, field) + { + if(_.isEmpty(rows)) + { + return; + } + var model = new instance.web.Model(self.fields[field].relation); + deferrends.push(model.call( + 'name_get', + [_.map(_.keys(rows), function(key) {return parseInt(key)})]) + .then(function(names) + { + _.each(names, function(name) + { + _.each(rows[name[0]], function(row) + { + row[field] = name; + }); + }); + })); + }) + return jQuery.when.apply(jQuery, deferrends); + }); + }); + }); + }, + + // get x axis values in the correct order + get_x_axis_values: function() + { + return _.keys(this.by_x_axis); + }, + + // get y axis values in the correct order + get_y_axis_values: function() + { + return _.keys(this.by_y_axis); + }, + + // get x axis labels + get_x_axis_labels: function() + { + var self = this; + return _.map( + this.get_x_axis_values(), + function(val) + { + return self.get_field_value( + _.first(_.values(self.by_x_axis[val])), + self.field_label_x_axis, true); + }); + }, + + // get the label for a value on the y axis + get_y_axis_label: function(y) + { + return this.get_field_value( + _.first(_.values(this.by_y_axis[y])), + this.field_label_y_axis, true); + }, + + // return the class(es) the inputs should have + get_xy_value_class: function() + { + var classes = 'oe_form_field oe_form_required'; + if(this.is_numeric) + { + classes += ' oe_form_field_float'; + } + return classes; + }, + + // return row id of a coordinate + get_xy_id: function(x, y) + { + return this.by_x_axis[x][y]['id']; + }, + + // return the value of a coordinate + get_xy_value: function(x, y) + { + return this.get_field_value( + this.by_x_axis[x][y], this.field_value); + }, + + // validate a value + validate_xy_value: function(val) + { + return true; + }, + + // parse a value from user input + parse_xy_value: function(val) + { + if(this.is_numeric) + { + return parseFloat(val); + } + else + { + return val; + } + }, + + // format a value from the database for display + format_xy_value: function(val) + { + return instance.web.format_value( + val, {'type': this.fields[this.field_value].type}); + }, + + // compute totals + compute_totals: function() + { + var self = this, + totals_x = {}, + totals_y = {}; + return self.dataset.read_ids(self.dataset.ids).then(function(rows) + { + _.each(rows, function(row) + { + var key_x = self.get_field_value(row, self.field_x_axis), + key_y = self.get_field_value(row, self.field_y_axis); + totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value); + totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value); + }); + }).then(function() + { + _.each(totals_y, function(total, y) + { + self.$el.find( + _.str.sprintf('td.row_total[data-y="%s"]', y)).text( + self.format_xy_value(total)); + }); + _.each(totals_x, function(total, x) + { + self.$el.find( + _.str.sprintf('td.column_total[data-x="%s"]', x)).text( + self.format_xy_value(total)); + }); + }); + }, + + start: function() + { + var self = this; + this.$el.find('input').on( + 'change', + function() + { + var $this = jQuery(this), + val = $this.val() + if(self.validate_xy_value(val)) + { + data = {} + data[self.field_value] = self.parse_xy_value(val); + self.dataset.write($this.data('id'), data); + $this.parent().removeClass('oe_form_invalid'); + self.compute_totals(); + } + else + { + $this.parent().addClass('oe_form_invalid'); + } + + }); + this.compute_totals(); + return this._super.apply(this, arguments); + }, + + // deactivate view related functions + load_views: function() {}, + reload_current_view: function() {}, + get_active_view: function() {}, + }); +} diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml new file mode 100644 index 00000000..e29367a0 --- /dev/null +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -0,0 +1,33 @@ + + +
+ + + + + + + + + + + + + + + + +
+ + + Total
+ + + + +
Total + +
+
+
+
diff --git a/web_widget_x2many_2d_matrix/views/templates.xml b/web_widget_x2many_2d_matrix/views/templates.xml new file mode 100644 index 00000000..06934cc3 --- /dev/null +++ b/web_widget_x2many_2d_matrix/views/templates.xml @@ -0,0 +1,11 @@ + + + + + + From 9b65b5d85d2910979b191857527f125e2c1f04dc Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 11 Mar 2015 18:05:40 +0100 Subject: [PATCH 02/46] [UPD] readme --- web_widget_x2many_2d_matrix/README.rst | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 27a266b9..6be504c4 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -29,10 +29,28 @@ attributes:: +You can pass the following parameters: + +field_x_axis + The field that indicates the x value of a point +field_y_axis + The field that indicates the y value of a point +field_label_x_axis + Use another field to display in the table header +field_label_y_axis + Use another field to display in the table header +field_value + Show this field as value +show_row_totals + If field_value is a numeric field, calculate row totals +show_column_totals + If field_value is a numeric field, calculate column totals + Known issues / Roadmap ====================== -* ... +* no validation yet +* it would be better to instantiate the proper field widget and let it render the input Credits ======= From d545ae541237fc257c7409a85b8c230cde18deeb Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 12:22:37 +0100 Subject: [PATCH 03/46] [IMP] show column totals in table footer --- .../static/src/xml/web_widget_x2many_2d_matrix.xml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index e29367a0..fe3f82d3 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -14,19 +14,21 @@ - + - + + + Total - + - + From 8acfc51d7fb81c33542d97eca838b06086453f4d Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 12:27:24 +0100 Subject: [PATCH 04/46] [FIX] use odoo's parse_value --- .../static/src/js/web_widget_x2many_2d_matrix.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 3a754f42..b157a679 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -221,14 +221,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) // parse a value from user input parse_xy_value: function(val) { - if(this.is_numeric) - { - return parseFloat(val); - } - else - { - return val; - } + return instance.web.parse_value( + val, {'type': this.fields[this.field_value].type}); }, // format a value from the database for display From 6f84c7577ca3a841265795735001f40d91755fa0 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 15:27:25 +0100 Subject: [PATCH 05/46] [ADD] allow to open linked record of one of the axes is a many2one field --- .../src/css/web_widget_x2many_2d_matrix.css | 4 ++ .../src/js/web_widget_x2many_2d_matrix.js | 47 ++++++++++++++----- .../src/xml/web_widget_x2many_2d_matrix.xml | 8 ++-- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css index e69de29b..2992579d 100644 --- a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css +++ b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css @@ -0,0 +1,4 @@ +.oe_form_field_x2many_2d_matrix th.oe_link +{ + cursor: pointer; +} diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index b157a679..f9ebeb25 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -166,18 +166,12 @@ openerp.web_widget_x2many_2d_matrix = function(instance) return _.keys(this.by_y_axis); }, - // get x axis labels - get_x_axis_labels: function() + // get the label for a value on the x axis + get_x_axis_label: function(x) { - var self = this; - return _.map( - this.get_x_axis_values(), - function(val) - { - return self.get_field_value( - _.first(_.values(self.by_x_axis[val])), - self.field_label_x_axis, true); - }); + return this.get_field_value( + _.first(_.values(this.by_x_axis[x])), + this.field_label_x_axis, true); }, // get the label for a value on the y axis @@ -264,6 +258,36 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); }, + setup_many2one_axes: function() + { + if(this.fields[this.field_x_axis].type == 'many2one') + { + this.$el.find('th[data-x]').addClass('oe_link') + .click(_.partial( + this.proxy(this.many2one_axis_click), + this.field_x_axis, 'x')); + } + if(this.fields[this.field_y_axis].type == 'many2one') + { + this.$el.find('tr[data-y] th').addClass('oe_link') + .click(_.partial( + this.proxy(this.many2one_axis_click), + this.field_y_axis, 'y')); + } + }, + + many2one_axis_click: function(field, id_attribute, e) + { + this.do_action({ + type: 'ir.actions.act_window', + name: this.fields[field].string, + res_model: this.fields[field].relation, + res_id: jQuery(e.currentTarget).data(id_attribute), + views: [[false, 'form']], + target: 'current', + }) + }, + start: function() { var self = this; @@ -288,6 +312,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); this.compute_totals(); + this.setup_many2one_axes(); return this._super.apply(this, arguments); }, diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index fe3f82d3..625a2d20 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -1,18 +1,18 @@ -
+
- + - -
- - + + Total
From 7fff989504ab1f26daee87305d0b357a10e5466d Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 15:44:57 +0100 Subject: [PATCH 06/46] [IMP] handle readonly flag [ADD] show grand total [IMP] classify floats as floats --- .../static/src/js/web_widget_x2many_2d_matrix.js | 4 ++++ .../static/src/xml/web_widget_x2many_2d_matrix.xml | 9 +++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index f9ebeb25..df8b4930 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -230,6 +230,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) compute_totals: function() { var self = this, + grand_total = 0, totals_x = {}, totals_y = {}; return self.dataset.read_ids(self.dataset.ids).then(function(rows) @@ -240,6 +241,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) key_y = self.get_field_value(row, self.field_y_axis); totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value); totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value); + grand_total += self.get_field_value(row, self.field_value); }); }).then(function() { @@ -255,6 +257,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) _.str.sprintf('td.column_total[data-x="%s"]', x)).text( self.format_xy_value(total)); }); + self.$el.find('.grand_total').text( + self.format_xy_value(grand_total)) }); }, diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index 625a2d20..2950439c 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -14,19 +14,20 @@
+ - + + +
Total +
From 3b7529814201e0f4fbe26800f4a3abfe05db0851 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 15:29:09 +0100 Subject: [PATCH 07/46] [IMP] collapse whitespace in rows --- .../static/src/css/web_widget_x2many_2d_matrix.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css index 2992579d..d33d4f21 100644 --- a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css +++ b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css @@ -2,3 +2,7 @@ { cursor: pointer; } +.openerp .oe_form_field_x2many_2d_matrix .oe_list_content > tbody > tr > td.oe_list_field_cell +{ + white-space: normal; +} From 3abe635c6427bd8f83f91f8153f9ff73b0af6443 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 15:34:02 +0100 Subject: [PATCH 08/46] [IMP] support changing readonly state --- .../static/src/js/web_widget_x2many_2d_matrix.js | 13 +++++++++++++ .../static/src/xml/web_widget_x2many_2d_matrix.xml | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index df8b4930..058a682e 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -317,9 +317,22 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); this.compute_totals(); this.setup_many2one_axes(); + this.on("change:effective_readonly", + this, this.proxy(this.effective_readonly_change)); + this.effective_readonly_change(); return this._super.apply(this, arguments); }, + effective_readonly_change: function() + { + this.$el + .find('tbody td.oe_list_field_cell span.oe_form_field>input') + .toggle(!this.get('effective_readonly')); + this.$el + .find('tbody td.oe_list_field_cell span.oe_form_field>span') + .toggle(this.get('effective_readonly')); + }, + // deactivate view related functions load_views: function() {}, reload_current_view: function() {}, diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index 2950439c..4f587e3f 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -16,8 +16,8 @@ - - + + From 9590e24f5a3382eb81e67663751aaeaf52675b34 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 17:36:39 +0100 Subject: [PATCH 09/46] [FIX] update readonly value after editing --- .../static/src/js/web_widget_x2many_2d_matrix.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 058a682e..9a7b8ca6 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -305,6 +305,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) { data = {} data[self.field_value] = self.parse_xy_value(val); + $this.siblings('span').text( + self.format_xy_value(self.parse_xy_value(val))); self.dataset.write($this.data('id'), data); $this.parent().removeClass('oe_form_invalid'); self.compute_totals(); From 6cc4fe601f164e9d0998fc0192eb65c0ff83de61 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 17:44:41 +0100 Subject: [PATCH 10/46] [IMP] pass computed totals to inheriting function --- .../static/src/js/web_widget_x2many_2d_matrix.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 9a7b8ca6..0425468b 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -259,6 +259,11 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); self.$el.find('.grand_total').text( self.format_xy_value(grand_total)) + return { + totals_x: totals_x, + totals_y: totals_y, + grand_total: grand_total, + }; }); }, From 3cc76e90487a9a9b4b0db97f825cef0e2f825ee5 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 18:19:42 +0100 Subject: [PATCH 11/46] [FIX] replace therp icon --- .../static/description/icon.png | Bin 1142 -> 12361 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/description/icon.png b/web_widget_x2many_2d_matrix/static/description/icon.png index 4c7ab302908e114888446d84d3493fa726033c1f..2c8e1ad59d7db69e97b2467c6fb0d4e3e460c9b4 100644 GIT binary patch literal 12361 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 YID`P*yI)_SqJKa}LQ%X*)WH9L08=sy2LJ#7 literal 1142 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpuXS$ zpAc7|0+Vy!%`Sd3J@*~Rz=H@Xz@vBMNCCrhU++~OAXQKjgnPb5^?zLm6yRy4l)f7mx|d; zx?*(k%?4)UEmyi0Ecrc2=k&YFn|8nX@qd4)(saLN%zo##oL4V9SpH%8W(I{5_Kby- zneS~VhopAyb6lBS&$U5E`gKppbdV7NQIC+SE zKe2ts8LoR*Hw$jqcIEDHSU_mi4;HoUDLTgOJMIHx zO|`@|q9i4;B-JXpC>2OC7#SEE>l#?<8d`)H8e5qfSQ#5>8yHy`7 Date: Fri, 13 Mar 2015 18:26:29 +0100 Subject: [PATCH 12/46] [FIX] setup focus --- .../static/src/js/web_widget_x2many_2d_matrix.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 0425468b..191ffad6 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -338,6 +338,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) this.$el .find('tbody td.oe_list_field_cell span.oe_form_field>span') .toggle(this.get('effective_readonly')); + this.$el.find('input').first().focus(); }, // deactivate view related functions From d5c0a478b9a823b9e1282d5b71b64847ccc9097c Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 18:47:15 +0100 Subject: [PATCH 13/46] [ADD] validation [IMP] write formatted value to back to input --- web_widget_x2many_2d_matrix/README.rst | 3 +-- .../src/js/web_widget_x2many_2d_matrix.js | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 6be504c4..5fb296be 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -49,8 +49,7 @@ show_column_totals Known issues / Roadmap ====================== -* no validation yet -* it would be better to instantiate the proper field widget and let it render the input +* it would be worth trying to instantiate the proper field widget and let it render the input Credits ======= diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 191ffad6..0c818548 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -209,6 +209,14 @@ openerp.web_widget_x2many_2d_matrix = function(instance) // validate a value validate_xy_value: function(val) { + try + { + this.parse_xy_value(val); + } + catch(e) + { + return false; + } return true; }, @@ -308,10 +316,12 @@ openerp.web_widget_x2many_2d_matrix = function(instance) val = $this.val() if(self.validate_xy_value(val)) { - data = {} - data[self.field_value] = self.parse_xy_value(val); - $this.siblings('span').text( - self.format_xy_value(self.parse_xy_value(val))); + var data = {}, value = self.parse_xy_value(val); + data[self.field_value] = value; + + $this.siblings('span').text(self.format_xy_value(value)); + $this.val(self.format_xy_value(value)); + self.dataset.write($this.data('id'), data); $this.parent().removeClass('oe_form_invalid'); self.compute_totals(); @@ -341,6 +351,11 @@ openerp.web_widget_x2many_2d_matrix = function(instance) this.$el.find('input').first().focus(); }, + is_syntax_valid: function() + { + return this.$el.find('.oe_form_invalid').length == 0; + }, + // deactivate view related functions load_views: function() {}, reload_current_view: function() {}, From e63ade61934b0974303fbdb0f7ce7242c0d27433 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 16:35:17 +0100 Subject: [PATCH 14/46] [IMP] we don't need data-x and data-y on the input --- .../static/src/xml/web_widget_x2many_2d_matrix.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index 4f587e3f..952a003f 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -14,9 +14,9 @@ - + - + From 81181c5ee67d5fa07c669853bb3f55c4c19657c1 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 16:47:21 +0100 Subject: [PATCH 15/46] [IMP] use semantic css classes instead of element names [RFR] and being on it, make reacting to changes in overrides simple --- .../src/js/web_widget_x2many_2d_matrix.js | 55 ++++++++++--------- .../src/xml/web_widget_x2many_2d_matrix.xml | 4 +- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 0c818548..12a56c8c 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -308,30 +308,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) start: function() { var self = this; - this.$el.find('input').on( - 'change', - function() - { - var $this = jQuery(this), - val = $this.val() - if(self.validate_xy_value(val)) - { - var data = {}, value = self.parse_xy_value(val); - data[self.field_value] = value; - - $this.siblings('span').text(self.format_xy_value(value)); - $this.val(self.format_xy_value(value)); - - self.dataset.write($this.data('id'), data); - $this.parent().removeClass('oe_form_invalid'); - self.compute_totals(); - } - else - { - $this.parent().addClass('oe_form_invalid'); - } - - }); + this.$el.find('.edit').on( + 'change', self.proxy(this.xy_value_change)); this.compute_totals(); this.setup_many2one_axes(); this.on("change:effective_readonly", @@ -340,15 +318,38 @@ openerp.web_widget_x2many_2d_matrix = function(instance) return this._super.apply(this, arguments); }, + xy_value_change: function(e) + { + var $this = jQuery(e.currentTarget), + val = $this.val(); + if(this.validate_xy_value(val)) + { + var data = {}, value = this.parse_xy_value(val); + data[this.field_value] = value; + + $this.siblings('.read').text(this.format_xy_value(value)); + $this.val(this.format_xy_value(value)); + + this.dataset.write($this.data('id'), data); + $this.parent().removeClass('oe_form_invalid'); + this.compute_totals(); + } + else + { + $this.parent().addClass('oe_form_invalid'); + } + + }, + effective_readonly_change: function() { this.$el - .find('tbody td.oe_list_field_cell span.oe_form_field>input') + .find('tbody td.oe_list_field_cell span.oe_form_field .edit') .toggle(!this.get('effective_readonly')); this.$el - .find('tbody td.oe_list_field_cell span.oe_form_field>span') + .find('tbody td.oe_list_field_cell span.oe_form_field .read') .toggle(this.get('effective_readonly')); - this.$el.find('input').first().focus(); + this.$el.find('.edit').first().focus(); }, is_syntax_valid: function() diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index 952a003f..35f1669b 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -16,8 +16,8 @@ - - + + From 48b086e6bfbd9a3887de42bdbd505e60ac97ae17 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 17:20:43 +0100 Subject: [PATCH 16/46] [IMP] add screenshot, example in README --- web_widget_x2many_2d_matrix/README.rst | 25 +++++++++++------- .../static/description/screenshot.png | Bin 0 -> 19577 bytes 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 web_widget_x2many_2d_matrix/static/description/screenshot.png diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 5fb296be..0b145aaf 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -4,16 +4,22 @@ This module allows to show an x2many field with 3-tuples ($x_value, $y_value, $value) in a table -+-----------+-----------+-----------+ -| | $x_value1 | $x_value2 | -+===========+===========+===========+ -| $y_value1 | $value1/1 | $value2/1 | -+-----------+-----------+-----------+ -| $y_value2 | $value1/2 | $value2/2 | -+-----------+-----------+-----------+ + $x_value1 $x_value2 +========= =========== =========== +$y_value1 $value(1/1) $value(2/1) +$y_value2 $value(1/2) $value(2/2) +========= =========== =========== -where `valuen/n` is editable. +where `value(n/n)` is editable. +An example use case would be: Select some projects and some employees so that +a manager can easily fill in the planned_hours for one task per employee. The +result could look like this: + +.. image:: /web_widget_x2many_2d_matrix/static/description/screenshot.png + :alt: Screenshot + +The beauty of this is that you have an arbitrary amount of columns with this widget, trying to get this in standard x2many lists involves some quite agly hacks. Usage ===== @@ -26,8 +32,7 @@ This assumes that my_field refers to a model with the fields `x`, `y` and `value`. If your fields are named differently, pass the correct names as attributes:: - + You can pass the following parameters: diff --git a/web_widget_x2many_2d_matrix/static/description/screenshot.png b/web_widget_x2many_2d_matrix/static/description/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..47c2a40d62b23f3b5c4b97dbc699f20dc5baa5ba GIT binary patch literal 19577 zcmdVC2{e^$`#!8vX%Y!#h!7%_%wx1ELp-K!rXqxGo->q)2xUm-h$J?Nz0D*cWS*yF z9=3U&{m+e__j#Z9dDri^{_FeJw?3;y>)!Wp-S>T6=XspRah&`49}2fl9j7`@KtOOx z<~Bl^fZ$Lt0l@)XqJywyo4foJ{5fPRDWgh6MAY4{_=|vmoaGSRCs-Pp1@#&m`gIq70YLVD)@G0D*G1@G7Ha`E?hJr$+m(d;%U zPY6h>P7^9IiC~pdpX;1VJaCzWk%Z_P{ndyc$AsNCQ5Fn8k3Vl_Nu?`=SL^w`qpA3IR^cgQx>#36k%KQ zx!9+&v{v-dTjJB$E{1|p{o{+*1$Ixif8MeYwX;&)v~6{I=%<2#gLXnOCRz2Seas9` zPuQn_KuP`UHVbZc%b-$GqNy#|USkXvz45sxfSTX;c9L?8z#Q!X{4Enc`Sz_K>6TvL zVB#$eZ!I*1lu{S_9R-Esi^uVwzbz1Un0z6d^m#f<_EGxrxxgD-r}2mV;xqG3#cZoJ zHZ;)tCCDb_yXJ{C)EvTpM9*MqV)9F%POICm7m43MB)yz`oK=-A41*=#-QnDA4Eofm zQ``}g4k-3s!BgxFX1hn`qRaBoOs&}Co^O>?(slX|<@4e9f83qeg$$53K%>I!S7~-% z)Z@pte{_FDrXH4Pz%3onqr&g@g_bQ$mN`UbBdsYm^`ti%zm3G)T(Mxn$pVKl%Q;vi zg5N~Izz{o{WUHMdsvaT#EQAohiGHj@JmzOQsyuJ+D3LS_8b(*yh5RbBdp#0)nrNoG zSI;GOwNdj&9x4>V9|dO`aMoLQy@HEPz0x4JyW<#@sW$$((T~}EK$Tmt<9G8q(t^c* zc%wlAwYP!kf8O9Nn7sYIJ{W#t^z(!(WB~ z@d0%h{yZk}w%s2?M_l(uqQzi(r`EQ%i6s{al7mGr=`U?@%HFsy;2KVGai-cF6gAC?;%McR z_wo^+&DzsXi*GiV^I3Ldr~J8!Nu;iEaGQwxdY6@z z)i*G3yVlbEy6dWy{*}{Tej4{p5Jr4CX7h@Xfm_py&%eWHAhNrw`-fzfl7%^$cGuw$ zB_2&L$C0~_@Z;(;Jo5C(sB@U?aJj#1fn9BFEh#B!fx{F-LT`a1oru#_Vc|z`?eg++ zZ*T9mdw1^Kk$z`g6?{pg-m)iGiNq^OE_}i+pFeEot)H!yv)fA(J<~w)t z$kS5c0yLI9z0LDLR;P&dz_;(;Z!@rQa9m0V4Lut;Xk%-8npTL%aa!l$oAUcI3JOTJ zFhom3!^_vNnK8JDiNeo(v#;O3kHWQaq!&)dv`EUNokn?u%jscOP3)?c;$a5o2jb$7MJidu?m+-?sF z2q2ewS&%;2@b+lvGmsLIs1cp#qTU#SqnwpwgS`Y}TfQn>&O4s

q$zofJg&#hQSr2BbXSC%$N*Ni65_QQ{ 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 Date: Mon, 16 Mar 2015 17:20:59 +0100 Subject: [PATCH 17/46] [IMP] icon --- .../static/description/icon.png | Bin 12361 -> 5139 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/description/icon.png b/web_widget_x2many_2d_matrix/static/description/icon.png index 2c8e1ad59d7db69e97b2467c6fb0d4e3e460c9b4..d7cdcec3b4f3db5e2af2745392b116e16a2e40b4 100644 GIT binary patch literal 5139 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 literal 12361 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 YID`P*yI)_SqJKa}LQ%X*)WH9L08=sy2LJ#7 From d797ed948d4e5d986279c676f97c6e0242cb1cbd Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 18 Mar 2015 17:10:29 +0100 Subject: [PATCH 18/46] [IMP] better modularity --- .../src/js/web_widget_x2many_2d_matrix.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 12a56c8c..d4828b47 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -110,12 +110,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) // setup data structure _.each(rows, function(row) { - var x = self.get_field_value(row, self.field_x_axis), - y = self.get_field_value(row, self.field_y_axis); - self.by_x_axis[x] = self.by_x_axis[x] || {}; - self.by_y_axis[y] = self.by_y_axis[y] || {}; - self.by_x_axis[x][y] = row; - self.by_y_axis[y][x] = row; + self.add_xy_row(row); _.each(read_many2one, function(rows, field) { if(!_.isArray(row[field])) @@ -154,6 +149,17 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); }, + // to whatever needed to setup internal data structure + add_xy_row: function(row) + { + var x = this.get_field_value(row, this.field_x_axis), + y = this.get_field_value(row, this.field_y_axis); + this.by_x_axis[x] = this.by_x_axis[x] || {}; + this.by_y_axis[y] = this.by_y_axis[y] || {}; + this.by_x_axis[x][y] = row; + this.by_y_axis[y][x] = row; + }, + // get x axis values in the correct order get_x_axis_values: function() { From 052a12ac367231f975b56daed07adc60d604f813 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 16 Apr 2015 09:59:47 +0200 Subject: [PATCH 19/46] [FIX] support rerendering after virtual ids change this is necessary for correct operation after creating new records --- .../static/src/js/web_widget_x2many_2d_matrix.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index d4828b47..e1021457 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -143,6 +143,13 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); })); }) + if(self.is_started && !self.no_rerender) + { + self.renderElement(); + self.$el.find('.edit').on( + 'change', self.proxy(self.xy_value_change)); + self.effective_readonly_change(); + } return jQuery.when.apply(jQuery, deferrends); }); }); From f59dfc378ff8c210c9d2af52000a49301f2c142e Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 16 Apr 2015 10:15:39 +0200 Subject: [PATCH 20/46] [FIX] also reinitialize totals --- .../static/src/js/web_widget_x2many_2d_matrix.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index e1021457..5d4ce785 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -146,6 +146,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) if(self.is_started && !self.no_rerender) { self.renderElement(); + self.compute_totals(); + self.setup_many2one_axes(); self.$el.find('.edit').on( 'change', self.proxy(self.xy_value_change)); self.effective_readonly_change(); From 58126af6ded6234a39418fb8f675ea130333efb1 Mon Sep 17 00:00:00 2001 From: Yannick Vaucher Date: Fri, 22 May 2015 19:45:36 +0200 Subject: [PATCH 21/46] Add bug tracker link on README.rst --- web_widget_x2many_2d_matrix/README.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 0b145aaf..a6b436e1 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -56,6 +56,16 @@ Known issues / Roadmap * it would be worth trying to instantiate the proper field widget and let it render the input + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed feedback +`here `_. + + Credits ======= From 5cc931e94b2910267c9cf3eb791a8f1c73733b5f Mon Sep 17 00:00:00 2001 From: Markus Schneider Date: Thu, 4 Jun 2015 14:30:25 +0200 Subject: [PATCH 22/46] add OCA to author --- web_widget_x2many_2d_matrix/__openerp__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index 1cbc4aad..95a3299b 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -21,7 +21,8 @@ { "name": "2D matrix for x2many fields", "version": "1.0", - "author": "Therp BV", + "author": "Therp BV, " + "Odoo Community Association (OCA)",, "license": "AGPL-3", "category": "Hidden/Dependency", "summary": "Show list fields as a matrix", From d05f5564140f738bcd634d4df99000d750eabf58 Mon Sep 17 00:00:00 2001 From: Markus Schneider Date: Fri, 5 Jun 2015 00:33:22 +0200 Subject: [PATCH 23/46] remove comma --- web_widget_x2many_2d_matrix/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index 95a3299b..2e43203a 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -22,7 +22,7 @@ "name": "2D matrix for x2many fields", "version": "1.0", "author": "Therp BV, " - "Odoo Community Association (OCA)",, + "Odoo Community Association (OCA)", "license": "AGPL-3", "category": "Hidden/Dependency", "summary": "Show list fields as a matrix", From a66512d89b7f92bde3e45fbb761aaa56685a57aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Fri, 9 Oct 2015 10:03:39 +0200 Subject: [PATCH 24/46] [UPD] prefix versions with 8.0 --- web_widget_x2many_2d_matrix/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index 2e43203a..0b652e3c 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -20,7 +20,7 @@ ############################################################################## { "name": "2D matrix for x2many fields", - "version": "1.0", + "version": "8.0.1.0.0", "author": "Therp BV, " "Odoo Community Association (OCA)", "license": "AGPL-3", From b0eb63ebfcbb65df5ab49a5702086a17e54cefec Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 14 Oct 2015 02:57:05 +0200 Subject: [PATCH 25/46] [MIG] Make modules uninstallable --- web_widget_x2many_2d_matrix/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index 0b652e3c..e48c3a6e 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -38,7 +38,7 @@ "test": [ ], "auto_install": False, - "installable": True, + 'installable': False, "application": False, "external_dependencies": { 'python': [], From a2a90729c9e4d187a725a90e694c07d72f4ee7ad Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Mon, 23 Nov 2015 23:43:23 -0500 Subject: [PATCH 26/46] OCA Transbot updated translations from Transifex --- web_widget_x2many_2d_matrix/i18n/ar.po | 27 +++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/de.po | 27 +++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/es.po | 26 ++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/fi.po | 27 +++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/fr.po | 26 ++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/hr.po | 27 +++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/it.po | 26 ++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/pt_BR.po | 26 ++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/sl.po | 26 ++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/tr.po | 27 +++++++++++++++++++++++ 10 files changed, 265 insertions(+) create mode 100644 web_widget_x2many_2d_matrix/i18n/ar.po create mode 100644 web_widget_x2many_2d_matrix/i18n/de.po create mode 100644 web_widget_x2many_2d_matrix/i18n/es.po create mode 100644 web_widget_x2many_2d_matrix/i18n/fi.po create mode 100644 web_widget_x2many_2d_matrix/i18n/fr.po create mode 100644 web_widget_x2many_2d_matrix/i18n/hr.po create mode 100644 web_widget_x2many_2d_matrix/i18n/it.po create mode 100644 web_widget_x2many_2d_matrix/i18n/pt_BR.po create mode 100644 web_widget_x2many_2d_matrix/i18n/sl.po create mode 100644 web_widget_x2many_2d_matrix/i18n/tr.po diff --git a/web_widget_x2many_2d_matrix/i18n/ar.po b/web_widget_x2many_2d_matrix/i18n/ar.po new file mode 100644 index 00000000..7a85d2bd --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/ar.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# SaFi J. , 2015 +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-12-16 07:41+0000\n" +"PO-Revision-Date: 2015-12-16 17:24+0000\n" +"Last-Translator: SaFi J. \n" +"Language-Team: Arabic (http://www.transifex.com/oca/OCA-web-8-0/language/ar/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ar\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "المجموع الاجمالي" diff --git a/web_widget_x2many_2d_matrix/i18n/de.po b/web_widget_x2many_2d_matrix/i18n/de.po new file mode 100644 index 00000000..337d2b94 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/de.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Rudolf Schnapka , 2016 +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-10 07:31+0000\n" +"PO-Revision-Date: 2016-01-18 20:15+0000\n" +"Last-Translator: Rudolf Schnapka \n" +"Language-Team: German (http://www.transifex.com/oca/OCA-web-8-0/language/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Gesamt" diff --git a/web_widget_x2many_2d_matrix/i18n/es.po b/web_widget_x2many_2d_matrix/i18n/es.po new file mode 100644 index 00000000..10ba2f9f --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/es.po @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-11-23 13:46+0000\n" +"PO-Revision-Date: 2015-11-07 11:29+0000\n" +"Last-Translator: Pedro M. Baeza \n" +"Language-Team: Spanish (http://www.transifex.com/oca/OCA-web-8-0/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Total" diff --git a/web_widget_x2many_2d_matrix/i18n/fi.po b/web_widget_x2many_2d_matrix/i18n/fi.po new file mode 100644 index 00000000..df37d34a --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/fi.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Jarmo Kortetjärvi , 2016 +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-10 07:31+0000\n" +"PO-Revision-Date: 2016-02-01 09:54+0000\n" +"Last-Translator: Jarmo Kortetjärvi \n" +"Language-Team: Finnish (http://www.transifex.com/oca/OCA-web-8-0/language/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Yhteensä" diff --git a/web_widget_x2many_2d_matrix/i18n/fr.po b/web_widget_x2many_2d_matrix/i18n/fr.po new file mode 100644 index 00000000..7ed8bc35 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/fr.po @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-05-06 15:50+0000\n" +"PO-Revision-Date: 2015-11-07 11:22+0000\n" +"Last-Translator: <>\n" +"Language-Team: French (http://www.transifex.com/oca/OCA-web-8-0/language/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Total" diff --git a/web_widget_x2many_2d_matrix/i18n/hr.po b/web_widget_x2many_2d_matrix/i18n/hr.po new file mode 100644 index 00000000..f209e294 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/hr.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Ana-Maria Olujić , 2016 +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-25 00:51+0000\n" +"PO-Revision-Date: 2016-08-19 11:47+0000\n" +"Last-Translator: Ana-Maria Olujić \n" +"Language-Team: Croatian (http://www.transifex.com/oca/OCA-web-8-0/language/hr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: hr\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Ukupno" diff --git a/web_widget_x2many_2d_matrix/i18n/it.po b/web_widget_x2many_2d_matrix/i18n/it.po new file mode 100644 index 00000000..5b5d0bf3 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/it.po @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-03-17 07:30+0000\n" +"PO-Revision-Date: 2015-11-07 11:22+0000\n" +"Last-Translator: <>\n" +"Language-Team: Italian (http://www.transifex.com/oca/OCA-web-8-0/language/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Totale" diff --git a/web_widget_x2many_2d_matrix/i18n/pt_BR.po b/web_widget_x2many_2d_matrix/i18n/pt_BR.po new file mode 100644 index 00000000..c56e07fa --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/pt_BR.po @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-03-11 02:18+0000\n" +"PO-Revision-Date: 2016-03-05 16:20+0000\n" +"Last-Translator: danimaribeiro \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/oca/OCA-web-8-0/language/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Total" diff --git a/web_widget_x2many_2d_matrix/i18n/sl.po b/web_widget_x2many_2d_matrix/i18n/sl.po new file mode 100644 index 00000000..07ae09c5 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/sl.po @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-11-23 13:46+0000\n" +"PO-Revision-Date: 2015-11-08 05:48+0000\n" +"Last-Translator: Matjaž Mozetič \n" +"Language-Team: Slovenian (http://www.transifex.com/oca/OCA-web-8-0/language/sl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sl\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Skupaj" diff --git a/web_widget_x2many_2d_matrix/i18n/tr.po b/web_widget_x2many_2d_matrix/i18n/tr.po new file mode 100644 index 00000000..635773bd --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/tr.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Ahmet Altınışık , 2015 +msgid "" +msgstr "" +"Project-Id-Version: web (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-08 21:34+0000\n" +"PO-Revision-Date: 2015-12-30 22:00+0000\n" +"Last-Translator: Ahmet Altınışık \n" +"Language-Team: Turkish (http://www.transifex.com/oca/OCA-web-8-0/language/tr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: tr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Toplam" From 3841f92900b96f438de38a2600900a362b237ff6 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 18 Jan 2016 16:41:25 +0100 Subject: [PATCH 27/46] [IMP] web_widget_x2many_2d_matrix: Several improvements * README update to newest OCA template * Example in README * Massive performance boost for big matrices, specially on Firefox * Assign id on row in order to find it back in all cases * Fix #321, choked on cached writes --- web_widget_x2many_2d_matrix/README.rst | 48 ++++++-- .../src/js/web_widget_x2many_2d_matrix.js | 112 ++++++++++++------ 2 files changed, 120 insertions(+), 40 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index a6b436e1..7c880b13 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -1,3 +1,7 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +=========================== 2D matrix for x2many fields =========================== @@ -51,12 +55,41 @@ show_row_totals show_column_totals If field_value is a numeric field, calculate column totals +Example +======= + +You need a data structure already filled with values. Let's assume we want to use this widget in a wizard that lets the user fill in planned hours for one task per project per user. In this case, we can use ``project.task`` as our data model and point to it from our wizard. The crucial part is that we fill the field in the default function:: + + class MyWizard(models.TransientModel): + _name = 'my.wizard' + + def _default_task_ids(self): + # your list of project should come from the context, some selection + # in a previous wizard or wherever else + projects = self.env['project.project'].browse([1, 2, 3]) + # same with users + users = self.env['res.users'].browse([1, 2, 3]) + return [ + (0, 0, {'project_id': p.id, 'user_id': u.id, 'planned_hours': 0}) + # if the project doesn't have a task for the user, create a new one + if not p.task_ids.filtered(lambda x: x.user_id == u) else + # otherwise, return the task + (4, p.task_ids.filtered(lambda x: x.user_id == u)[0].id) + for p in projects + for u in users + ] + + task_ids = fields.Many2many('project.task', default=_default_task_ids) + +Now in our wizard, we can use:: + + + Known issues / Roadmap ====================== * it would be worth trying to instantiate the proper field widget and let it render the input - Bug Tracker =========== @@ -65,7 +98,6 @@ In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed feedback `here `_. - Credits ======= @@ -77,12 +109,14 @@ Contributors Maintainer ---------- -.. image:: http://odoo-community.org/logo.png - :alt: Odoo Community Association - :target: http://odoo-community.org +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org This module is maintained by the OCA. -OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. -To contribute to this module, please visit http://odoo-community.org. +To contribute to this module, please visit https://odoo-community.org. diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 5d4ce785..4dbcb4cc 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -31,6 +31,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) // those will be filled with rows from the dataset by_x_axis: {}, by_y_axis: {}, + by_id: {}, + // configuration values field_x_axis: 'x', field_label_x_axis: 'x', field_y_axis: 'y', @@ -81,7 +83,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) self.by_x_axis = {}; self.by_y_axis = {}; - + self.by_id = {}; + return jQuery.when(result).then(function() { return self.dataset._model.call('fields_get').then(function(fields) @@ -90,7 +93,35 @@ openerp.web_widget_x2many_2d_matrix = function(instance) self.is_numeric = fields[self.field_value].type == 'float'; self.show_row_totals &= self.is_numeric; self.show_column_totals &= self.is_numeric; - }).then(function() + }) + // if there are cached writes on the parent dataset, read below + // only returns the written data, which is not enough to properly + // set up our data structure. Read those ids here and patch the + // cache + .then(function() + { + var ids_written = _.map( + self.dataset.to_write, function(x) { return x.id }); + if(!ids_written.length) + { + return; + } + return (new instance.web.Query(self.dataset._model)) + .filter([['id', 'in', ids_written]]) + .all() + .then(function(rows) + { + _.each(rows, function(row) + { + var cache = _.find( + self.dataset.cache, + function(x) { return x.id == row.id } + ); + _.extend(cache.values, row, _.clone(cache.values)); + }) + }) + }) + .then(function() { return self.dataset.read_ids(self.dataset.ids).then(function(rows) { @@ -158,15 +189,31 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }); }, - // to whatever needed to setup internal data structure + // do whatever needed to setup internal data structure add_xy_row: function(row) { var x = this.get_field_value(row, this.field_x_axis), y = this.get_field_value(row, this.field_y_axis); + // row is a *copy* of a row in dataset.cache, fetch + // a reference to this row in order to have the + // internal data structure point to the same data + // the dataset manipulates + _.every(this.dataset.cache, function(cached_row) + { + if(cached_row.id == row.id) + { + row = cached_row.values; + // new rows don't have that + row.id = cached_row.id; + return false; + } + return true; + }); this.by_x_axis[x] = this.by_x_axis[x] || {}; this.by_y_axis[y] = this.by_y_axis[y] || {}; this.by_x_axis[x][y] = row; this.by_y_axis[y][x] = row; + this.by_id[row.id] = row; }, // get x axis values in the correct order @@ -255,39 +302,38 @@ openerp.web_widget_x2many_2d_matrix = function(instance) var self = this, grand_total = 0, totals_x = {}, - totals_y = {}; - return self.dataset.read_ids(self.dataset.ids).then(function(rows) + totals_y = {}, + rows = this.by_id, + deferred = jQuery.Deferred(); + _.each(rows, function(row) { - _.each(rows, function(row) - { - var key_x = self.get_field_value(row, self.field_x_axis), - key_y = self.get_field_value(row, self.field_y_axis); - totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value); - totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value); - grand_total += self.get_field_value(row, self.field_value); - }); - }).then(function() + var key_x = self.get_field_value(row, self.field_x_axis), + key_y = self.get_field_value(row, self.field_y_axis); + totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value); + totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value); + grand_total += self.get_field_value(row, self.field_value); + }); + _.each(totals_y, function(total, y) { - _.each(totals_y, function(total, y) - { - self.$el.find( - _.str.sprintf('td.row_total[data-y="%s"]', y)).text( - self.format_xy_value(total)); - }); - _.each(totals_x, function(total, x) - { - self.$el.find( - _.str.sprintf('td.column_total[data-x="%s"]', x)).text( - self.format_xy_value(total)); - }); - self.$el.find('.grand_total').text( - self.format_xy_value(grand_total)) - return { - totals_x: totals_x, - totals_y: totals_y, - grand_total: grand_total, - }; + self.$el.find( + _.str.sprintf('td.row_total[data-y="%s"]', y)).text( + self.format_xy_value(total)); + }); + _.each(totals_x, function(total, x) + { + self.$el.find( + _.str.sprintf('td.column_total[data-x="%s"]', x)).text( + self.format_xy_value(total)); + }); + self.$el.find('.grand_total').text( + self.format_xy_value(grand_total)) + deferred.resolve({ + totals_x: totals_x, + totals_y: totals_y, + grand_total: grand_total, + rows: rows, }); + return deferred; }, setup_many2one_axes: function() From 2a2439b848d872c6febb209b8873bb594edc6335 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 14 Sep 2016 09:32:56 +0200 Subject: [PATCH 28/46] [IMP] web_widget_x2many_2d_matrix: New option field_att_ Declare as many options prefixed with this string as you need for binding a field value with an HTML node attribute (disabled, class, style...) called as the `` passed in the option. NOTE: This doesn't prevent to require to fill the full matrix with all the combination records. --- web_widget_x2many_2d_matrix/README.rst | 38 ++++++++++++---- web_widget_x2many_2d_matrix/__openerp__.py | 35 +++------------ .../src/js/web_widget_x2many_2d_matrix.js | 45 ++++++++++--------- .../src/xml/web_widget_x2many_2d_matrix.xml | 2 +- 4 files changed, 63 insertions(+), 57 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 7c880b13..83c29328 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -1,5 +1,6 @@ .. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :alt: License: AGPL-3 + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 =========================== 2D matrix for x2many fields @@ -8,7 +9,8 @@ This module allows to show an x2many field with 3-tuples ($x_value, $y_value, $value) in a table - $x_value1 $x_value2 +========= =========== =========== +\ $x_value1 $x_value2 ========= =========== =========== $y_value1 $value(1/1) $value(2/1) $y_value2 $value(1/2) $value(2/2) @@ -23,7 +25,9 @@ result could look like this: .. image:: /web_widget_x2many_2d_matrix/static/description/screenshot.png :alt: Screenshot -The beauty of this is that you have an arbitrary amount of columns with this widget, trying to get this in standard x2many lists involves some quite agly hacks. +The beauty of this is that you have an arbitrary amount of columns with this +widget, trying to get this in standard x2many lists involves some quite ugly +hacks. Usage ===== @@ -54,11 +58,23 @@ show_row_totals If field_value is a numeric field, calculate row totals show_column_totals If field_value is a numeric field, calculate column totals +field_att_ + Declare as many options prefixed with this string as you need for binding + a field value with an HTML node attribute (disabled, class, style...) + called as the `` passed in the option. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/162/8.0 Example ======= -You need a data structure already filled with values. Let's assume we want to use this widget in a wizard that lets the user fill in planned hours for one task per project per user. In this case, we can use ``project.task`` as our data model and point to it from our wizard. The crucial part is that we fill the field in the default function:: +You need a data structure already filled with values. Let's assume we want to +use this widget in a wizard that lets the user fill in planned hours for one +task per project per user. In this case, we can use ``project.task`` as our +data model and point to it from our wizard. The crucial part is that we fill +the field in the default function:: class MyWizard(models.TransientModel): _name = 'my.wizard' @@ -85,6 +101,11 @@ Now in our wizard, we can use:: +Note that all values in the matrix must exist, so you need to create them +previously if not present, but you can control visually the editability of +the fields in the matrix through `field_att_disabled` option with a control +field. + Known issues / Roadmap ====================== @@ -93,10 +114,10 @@ Known issues / Roadmap Bug Tracker =========== -Bugs are tracked on `GitHub Issues `_. -In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us smashing it by providing a detailed and welcomed feedback -`here `_. +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. Credits ======= @@ -105,6 +126,7 @@ Contributors ------------ * Holger Brunn +* Pedro M. Baeza Maintainer ---------- diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index e48c3a6e..87dc3541 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -1,27 +1,13 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 Therp BV . -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# Copyright 2015 Holger Brunn +# Copyright 2016 Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + { "name": "2D matrix for x2many fields", - "version": "8.0.1.0.0", + "version": "8.0.1.1.0", "author": "Therp BV, " + "Tecnativa," "Odoo Community Association (OCA)", "license": "AGPL-3", "category": "Hidden/Dependency", @@ -35,12 +21,5 @@ "qweb": [ 'static/src/xml/web_widget_x2many_2d_matrix.xml', ], - "test": [ - ], - "auto_install": False, - 'installable': False, - "application": False, - "external_dependencies": { - 'python': [], - }, + "installable": True, } diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 4dbcb4cc..5f6147f4 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -1,23 +1,6 @@ -//-*- coding: utf-8 -*- -//############################################################################ -// -// OpenERP, Open Source Management Solution -// This module copyright (C) 2015 Therp BV . -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// -//############################################################################ +/* Copyright 2015 Holger Brunn + * Copyright 2016 Pedro M. Baeza + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ openerp.web_widget_x2many_2d_matrix = function(instance) { @@ -44,6 +27,8 @@ openerp.web_widget_x2many_2d_matrix = function(instance) show_column_totals: true, // this will be filled with the model's fields_get fields: {}, + // Store fields used to fill HTML attributes + fields_att: {}, // read parameters init: function(field_manager, node) @@ -53,6 +38,12 @@ openerp.web_widget_x2many_2d_matrix = function(instance) this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis; this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis; this.field_value = node.attrs.field_value || this.field_value; + for (var property in node.attrs) { + if (property.startsWith("field_att_")) { + this.fields_att[property.substring(10)] = node.attrs[property]; + } + } + this.field_editability = node.attrs.field_editability || this.field_editability; this.show_row_totals = node.attrs.show_row_totals || this.show_row_totals; this.show_column_totals = node.attrs.show_column_totals || this.show_column_totals; return this._super.apply(this, arguments); @@ -261,6 +252,20 @@ openerp.web_widget_x2many_2d_matrix = function(instance) return this.by_x_axis[x][y]['id']; }, + get_xy_att: function(x, y) + { + var vals = {}; + for (var att in this.fields_att) { + var val = this.get_field_value( + this.by_x_axis[x][y], this.fields_att[att]); + // Discard empty values + if (val) { + vals[att] = val; + } + } + return vals; + }, + // return the value of a coordinate get_xy_value: function(x, y) { diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index 35f1669b..ca6b687f 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -16,7 +16,7 @@ - + From e3a2ca214f201512359d7273c9ce510ae26a0218 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Fri, 16 Sep 2016 14:35:54 +0200 Subject: [PATCH 29/46] [MIG] web_widget_x2many_2d_matrix: Migration to 9.0 --- web_widget_x2many_2d_matrix/README.rst | 9 ++++ web_widget_x2many_2d_matrix/__openerp__.py | 2 +- .../src/js/web_widget_x2many_2d_matrix.js | 53 ++++++++++++------- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 83c29328..dc8a480f 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -110,6 +110,15 @@ Known issues / Roadmap ====================== * it would be worth trying to instantiate the proper field widget and let it render the input +* If you pass values with an onchange, you need to overwrite the model's method + `onchange` for making the widget work:: + + @api.multi + def onchange(self, values, field_name, field_onchange): + if "one2many_field" in field_onchange: + for sub in []: + field_onchange.setdefault("one2many_field." + sub, u"") + return super(model, self).onchange(values, field_name, field_onchange) Bug Tracker =========== diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index 87dc3541..a8f4e8cf 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -5,7 +5,7 @@ { "name": "2D matrix for x2many fields", - "version": "8.0.1.1.0", + "version": "9.0.1.0.0", "author": "Therp BV, " "Tecnativa," "Odoo Community Association (OCA)", diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 5f6147f4..4087e88e 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -2,12 +2,16 @@ * Copyright 2016 Pedro M. Baeza * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ -openerp.web_widget_x2many_2d_matrix = function(instance) -{ - instance.web.form.widgets.add( - 'x2many_2d_matrix', - 'instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix'); - instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix = instance.web.form.FieldOne2Many.extend({ +odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { + "use strict"; + + var core = require('web.core'); + var formats = require('web.formats'); + var FieldOne2Many = core.form_widget_registry.get('one2many'); + var Model = require('web.Model'); + var data = require('web.data'); + + var WidgetX2Many2dMatrix = FieldOne2Many.extend({ template: 'FieldX2Many2dMatrix', widget_class: 'oe_form_field_x2many_2d_matrix', @@ -46,7 +50,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) this.field_editability = node.attrs.field_editability || this.field_editability; this.show_row_totals = node.attrs.show_row_totals || this.show_row_totals; this.show_column_totals = node.attrs.show_column_totals || this.show_column_totals; - return this._super.apply(this, arguments); + return this._super(field_manager, node); }, // return a field's value, id in case it's a one2many field @@ -67,10 +71,10 @@ openerp.web_widget_x2many_2d_matrix = function(instance) }, // setup our datastructure for simple access in the template - set_value: function() + set_value: function(value_) { var self = this, - result = this._super.apply(this, arguments); + result = this._super(value_); self.by_x_axis = {}; self.by_y_axis = {}; @@ -150,7 +154,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) { return; } - var model = new instance.web.Model(self.fields[field].relation); + var model = new Model(self.fields[field].relation); deferrends.push(model.call( 'name_get', [_.map(_.keys(rows), function(key) {return parseInt(key)})]) @@ -171,7 +175,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) self.compute_totals(); self.setup_many2one_axes(); self.$el.find('.edit').on( - 'change', self.proxy(self.xy_value_change)); + 'change', self.proxy(self.xy_value_change)); self.effective_readonly_change(); } return jQuery.when.apply(jQuery, deferrends); @@ -290,14 +294,14 @@ openerp.web_widget_x2many_2d_matrix = function(instance) // parse a value from user input parse_xy_value: function(val) { - return instance.web.parse_value( + return formats.parse_value( val, {'type': this.fields[this.field_value].type}); }, // format a value from the database for display format_xy_value: function(val) { - return instance.web.format_value( + return formats.format_value( val, {'type': this.fields[this.field_value].type}); }, @@ -381,7 +385,7 @@ openerp.web_widget_x2many_2d_matrix = function(instance) this.on("change:effective_readonly", this, this.proxy(this.effective_readonly_change)); this.effective_readonly_change(); - return this._super.apply(this, arguments); + return this._super(); }, xy_value_change: function(e) @@ -423,9 +427,20 @@ openerp.web_widget_x2many_2d_matrix = function(instance) return this.$el.find('.oe_form_invalid').length == 0; }, - // deactivate view related functions - load_views: function() {}, - reload_current_view: function() {}, - get_active_view: function() {}, + load_views: function() { + // Needed for removing the initial empty tree view when the widget + // is loaded + var self = this, + result = this._super(); + + return $.when(result).then(function() + { + self.set_value(false); + }); + }, }); -} + + core.form_widget_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix); + + return WidgetX2Many2dMatrix; +}); From d3cd13a5a81f056af91a44bc4e147a02baf1d4eb Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 16 Sep 2016 17:56:53 +0200 Subject: [PATCH 30/46] [IMP] web_widget_x2many_2d_matrix: Use new JS modularized API. --- .../static/src/js/web_widget_x2many_2d_matrix.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 4087e88e..e570949d 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -10,6 +10,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { var FieldOne2Many = core.form_widget_registry.get('one2many'); var Model = require('web.Model'); var data = require('web.data'); + var _ = require('_'); + var $ = require('$'); var WidgetX2Many2dMatrix = FieldOne2Many.extend({ template: 'FieldX2Many2dMatrix', @@ -80,7 +82,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { self.by_y_axis = {}; self.by_id = {}; - return jQuery.when(result).then(function() + return $.when(result).then(function() { return self.dataset._model.call('fields_get').then(function(fields) { @@ -101,7 +103,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { { return; } - return (new instance.web.Query(self.dataset._model)) + return (new data.Query(self.dataset._model)) .filter([['id', 'in', ids_written]]) .all() .then(function(rows) @@ -178,7 +180,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { 'change', self.proxy(self.xy_value_change)); self.effective_readonly_change(); } - return jQuery.when.apply(jQuery, deferrends); + return $.when.apply($, deferrends); }); }); }); @@ -313,7 +315,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { totals_x = {}, totals_y = {}, rows = this.by_id, - deferred = jQuery.Deferred(); + deferred = $.Deferred(); _.each(rows, function(row) { var key_x = self.get_field_value(row, self.field_x_axis), @@ -369,7 +371,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { type: 'ir.actions.act_window', name: this.fields[field].string, res_model: this.fields[field].relation, - res_id: jQuery(e.currentTarget).data(id_attribute), + res_id: $(e.currentTarget).data(id_attribute), views: [[false, 'form']], target: 'current', }) @@ -390,7 +392,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { xy_value_change: function(e) { - var $this = jQuery(e.currentTarget), + var $this = $(e.currentTarget), val = $this.val(); if(this.validate_xy_value(val)) { From 3123f7fa3044093f57c0a095daacdbbfd23c6704 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 21 Sep 2016 18:24:51 +0200 Subject: [PATCH 31/46] [IMP] web_widget_x2many_2d_matrix: Include x_axis_clickable and y_axis_clickable attrs XML attributes for the widget that allows to configure if the axis will be clickable or not in case the source field is a many2one field. --- web_widget_x2many_2d_matrix/README.rst | 12 +++++++++-- .../src/js/web_widget_x2many_2d_matrix.js | 20 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index dc8a480f..6628a81e 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -52,12 +52,20 @@ field_label_x_axis Use another field to display in the table header field_label_y_axis Use another field to display in the table header +x_axis_clickable + It indicates if the X axis allows to be clicked for navigating to the field + (if it's a many2one field). True by default +y_axis_clickable + It indicates if the Y axis allows to be clicked for navigating to the field + (if it's a many2one field). True by default field_value Show this field as value show_row_totals - If field_value is a numeric field, calculate row totals + If field_value is a numeric field, it indicates if you want to calculate + row totals. True by default show_column_totals - If field_value is a numeric field, calculate column totals + If field_value is a numeric field, it indicates if you want to calculate + column totals. True by default field_att_ Declare as many options prefixed with this string as you need for binding a field value with an HTML node attribute (disabled, class, style...) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index e570949d..c5631593 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -27,6 +27,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { field_y_axis: 'y', field_label_y_axis: 'y', field_value: 'value', + x_axis_clickable: true, + y_axis_clickable: true, // information about our datatype is_numeric: false, show_row_totals: true, @@ -36,6 +38,14 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { // Store fields used to fill HTML attributes fields_att: {}, + parse_boolean: function(val) + { + if (val.toLowerCase() === 'true' || val === '1') { + return true; + } + return false; + }, + // read parameters init: function(field_manager, node) { @@ -43,6 +53,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { this.field_y_axis = node.attrs.field_y_axis || this.field_y_axis; this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis; this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis; + this.x_axis_clickable = node.attrs.x_axis_clickable != undefined ? this.parse_boolean(node.attrs.x_axis_clickable) : this.x_axis_clickable; + this.y_axis_clickable = node.attrs.y_axis_clickable != undefined ? this.parse_boolean(node.attrs.y_axis_clickable) : this.y_axis_clickable; this.field_value = node.attrs.field_value || this.field_value; for (var property in node.attrs) { if (property.startsWith("field_att_")) { @@ -50,8 +62,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { } } this.field_editability = node.attrs.field_editability || this.field_editability; - this.show_row_totals = node.attrs.show_row_totals || this.show_row_totals; - this.show_column_totals = node.attrs.show_column_totals || this.show_column_totals; + this.show_row_totals = node.attrs.show_row_totals != undefined ? this.parse_boolean(node.attrs.show_row_totals) : this.show_row_totals; + this.show_column_totals = node.attrs.show_column_totals != undefined ? this.parse_boolean(node.attrs.show_column_totals) : this.show_column_totals; return this._super(field_manager, node); }, @@ -349,14 +361,14 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { setup_many2one_axes: function() { - if(this.fields[this.field_x_axis].type == 'many2one') + if(this.fields[this.field_x_axis].type == 'many2one' && this.x_axis_clickable) { this.$el.find('th[data-x]').addClass('oe_link') .click(_.partial( this.proxy(this.many2one_axis_click), this.field_x_axis, 'x')); } - if(this.fields[this.field_y_axis].type == 'many2one') + if(this.fields[this.field_y_axis].type == 'many2one' && this.y_axis_clickable) { this.$el.find('tr[data-y] th').addClass('oe_link') .click(_.partial( From 388fa3dd4bb3a71f5d8e46dd52709b7343d737e8 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 21 Sep 2016 20:39:15 +0200 Subject: [PATCH 32/46] [FIX] web_widget_x2many_2d_matrix: Use existing value in load_views --- .../static/src/js/web_widget_x2many_2d_matrix.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index c5631593..782ed21a 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -449,7 +449,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { return $.when(result).then(function() { - self.set_value(false); + self.set_value(self.get_value()); }); }, }); From 466561181fb1692b4c672e90b197bbd36484e83f Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 09:45:23 +0200 Subject: [PATCH 33/46] [IMP] web_widget_x2many_2d_matrix: Better options parsing --- .../static/src/js/web_widget_x2many_2d_matrix.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 782ed21a..3e182047 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -53,8 +53,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { this.field_y_axis = node.attrs.field_y_axis || this.field_y_axis; this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis; this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis; - this.x_axis_clickable = node.attrs.x_axis_clickable != undefined ? this.parse_boolean(node.attrs.x_axis_clickable) : this.x_axis_clickable; - this.y_axis_clickable = node.attrs.y_axis_clickable != undefined ? this.parse_boolean(node.attrs.y_axis_clickable) : this.y_axis_clickable; + this.x_axis_clickable = this.parse_boolean(node.attrs.x_axis_clickable || '1'); + this.y_axis_clickable = this.parse_boolean(node.attrs.y_axis_clickable || '1'); this.field_value = node.attrs.field_value || this.field_value; for (var property in node.attrs) { if (property.startsWith("field_att_")) { @@ -62,8 +62,8 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { } } this.field_editability = node.attrs.field_editability || this.field_editability; - this.show_row_totals = node.attrs.show_row_totals != undefined ? this.parse_boolean(node.attrs.show_row_totals) : this.show_row_totals; - this.show_column_totals = node.attrs.show_column_totals != undefined ? this.parse_boolean(node.attrs.show_column_totals) : this.show_column_totals; + this.show_row_totals = this.parse_boolean(node.attrs.show_row_totals || '1'); + this.show_column_totals = this.parse_boolean(node.attrs.show_column_totals || '1'); return this._super(field_manager, node); }, From c4d29811930b8954f46423caef2b475ea324408c Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 09:48:03 +0200 Subject: [PATCH 34/46] [IMP+ web_widget_x2many_2d_matrix: Add roadmap --- web_widget_x2many_2d_matrix/README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 6628a81e..85d5dd71 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -117,7 +117,9 @@ field. Known issues / Roadmap ====================== -* it would be worth trying to instantiate the proper field widget and let it render the input +* It would be worth trying to instantiate the proper field widget and let it render the input +* Let the widget deal with the missing values of the full Cartesian product, + instead of being forced to pre-fill all the possible values. * If you pass values with an onchange, you need to overwrite the model's method `onchange` for making the widget work:: From a517b40c8763526fb15376ba92bec3781750b97f Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 12:09:34 +0200 Subject: [PATCH 35/46] [IMP] web_widget_x2many_2d_matrix: Remove unneeded code --- .../src/js/web_widget_x2many_2d_matrix.js | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 3e182047..8d2e3fc6 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -134,55 +134,11 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { { return self.dataset.read_ids(self.dataset.ids).then(function(rows) { - var read_many2one = {}, - many2one_fields = [ - self.field_x_axis, self.field_y_axis, - self.field_label_x_axis, self.field_label_y_axis - ]; - // prepare to read many2one names if necessary (we can get (id, name) or just id as value) - _.each(many2one_fields, function(field) - { - if(self.fields[field].type == 'many2one') - { - read_many2one[field] = {}; - } - }); // setup data structure _.each(rows, function(row) { self.add_xy_row(row); - _.each(read_many2one, function(rows, field) - { - if(!_.isArray(row[field])) - { - rows[row[field]] = rows[row[field]] || [] - rows[row[field]].push(row); - } - }); }); - // read many2one fields if necessary - var deferrends = []; - _.each(read_many2one, function(rows, field) - { - if(_.isEmpty(rows)) - { - return; - } - var model = new Model(self.fields[field].relation); - deferrends.push(model.call( - 'name_get', - [_.map(_.keys(rows), function(key) {return parseInt(key)})]) - .then(function(names) - { - _.each(names, function(name) - { - _.each(rows[name[0]], function(row) - { - row[field] = name; - }); - }); - })); - }) if(self.is_started && !self.no_rerender) { self.renderElement(); @@ -192,7 +148,6 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { 'change', self.proxy(self.xy_value_change)); self.effective_readonly_change(); } - return $.when.apply($, deferrends); }); }); }); From 491b210416600646c9bb17e2cf45b7736bb1ca61 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 12:11:40 +0200 Subject: [PATCH 36/46] [FIX] web_widget_x2many_2d_matrix: Init correctly the view --- .../static/src/js/web_widget_x2many_2d_matrix.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 8d2e3fc6..8a51b3a6 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -404,7 +404,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { return $.when(result).then(function() { - self.set_value(self.get_value()); + self.renderElement(); }); }, }); From 03c1a97a9ef16d23e3f8b69dd5d32b2fac9a0e4c Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 6 Oct 2016 16:12:54 +0200 Subject: [PATCH 37/46] [MIG] Make modules uninstallable --- web_widget_x2many_2d_matrix/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__openerp__.py index a8f4e8cf..8b55c97d 100644 --- a/web_widget_x2many_2d_matrix/__openerp__.py +++ b/web_widget_x2many_2d_matrix/__openerp__.py @@ -21,5 +21,5 @@ "qweb": [ 'static/src/xml/web_widget_x2many_2d_matrix.xml', ], - "installable": True, + 'installable': False, } From e9b20fb5fc61804ac7c7dbeee61b3b27cecdb8a6 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 6 Oct 2016 16:13:01 +0200 Subject: [PATCH 38/46] [MIG] Rename manifest files --- web_widget_x2many_2d_matrix/{__openerp__.py => __manifest__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web_widget_x2many_2d_matrix/{__openerp__.py => __manifest__.py} (100%) diff --git a/web_widget_x2many_2d_matrix/__openerp__.py b/web_widget_x2many_2d_matrix/__manifest__.py similarity index 100% rename from web_widget_x2many_2d_matrix/__openerp__.py rename to web_widget_x2many_2d_matrix/__manifest__.py From 47c55d66b484e5cec8df49d876d7ef50e85371b4 Mon Sep 17 00:00:00 2001 From: jesusVMayor Date: Mon, 24 Apr 2017 12:28:47 +0200 Subject: [PATCH 39/46] Migration of web_widget_x2many_2d_matrix to 10.0 --- web_widget_x2many_2d_matrix/__manifest__.py | 4 ++-- .../static/src/css/web_widget_x2many_2d_matrix.css | 2 +- .../static/src/js/web_widget_x2many_2d_matrix.js | 7 +++---- .../static/src/xml/web_widget_x2many_2d_matrix.xml | 6 +++--- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/web_widget_x2many_2d_matrix/__manifest__.py b/web_widget_x2many_2d_matrix/__manifest__.py index 8b55c97d..b4651c07 100644 --- a/web_widget_x2many_2d_matrix/__manifest__.py +++ b/web_widget_x2many_2d_matrix/__manifest__.py @@ -5,7 +5,7 @@ { "name": "2D matrix for x2many fields", - "version": "9.0.1.0.0", + "version": "10.0.1.0.0", "author": "Therp BV, " "Tecnativa," "Odoo Community Association (OCA)", @@ -21,5 +21,5 @@ "qweb": [ 'static/src/xml/web_widget_x2many_2d_matrix.xml', ], - 'installable': False, + "installable": True, } diff --git a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css index d33d4f21..14ed1c53 100644 --- a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css +++ b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css @@ -2,7 +2,7 @@ { cursor: pointer; } -.openerp .oe_form_field_x2many_2d_matrix .oe_list_content > tbody > tr > td.oe_list_field_cell +.oe_form_field_x2many_2d_matrix .oe_list_content > tbody > tr > td.oe_list_field_cell { white-space: normal; } diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 8a51b3a6..43fa84bb 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -10,8 +10,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { var FieldOne2Many = core.form_widget_registry.get('one2many'); var Model = require('web.Model'); var data = require('web.data'); - var _ = require('_'); - var $ = require('$'); + var $ = require('jquery'); var WidgetX2Many2dMatrix = FieldOne2Many.extend({ template: 'FieldX2Many2dMatrix', @@ -383,10 +382,10 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { effective_readonly_change: function() { this.$el - .find('tbody td.oe_list_field_cell span.oe_form_field .edit') + .find('tbody .read') .toggle(!this.get('effective_readonly')); this.$el - .find('tbody td.oe_list_field_cell span.oe_form_field .read') + .find('tbody .read') .toggle(this.get('effective_readonly')); this.$el.find('.edit').first().focus(); }, diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index ca6b687f..a1a0d521 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -1,7 +1,7 @@

- +
- From 936e9c23ddb4b20b1f3dc10141d08abe25fca80e Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Fri, 28 Apr 2017 20:01:00 +0200 Subject: [PATCH 40/46] [IMP] web_widget_x2many_2d_matrix: Update example There are now more required fields for a task. --- web_widget_x2many_2d_matrix/README.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 85d5dd71..40098bff 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -84,6 +84,8 @@ task per project per user. In this case, we can use ``project.task`` as our data model and point to it from our wizard. The crucial part is that we fill the field in the default function:: + from odoo import fields, models + class MyWizard(models.TransientModel): _name = 'my.wizard' @@ -94,7 +96,13 @@ the field in the default function:: # same with users users = self.env['res.users'].browse([1, 2, 3]) return [ - (0, 0, {'project_id': p.id, 'user_id': u.id, 'planned_hours': 0}) + (0, 0, { + 'project_id': p.id, + 'user_id': u.id, + 'planned_hours': 0, + 'message_needaction': False, + 'date_deadline': fields.Date.today(), + }) # if the project doesn't have a task for the user, create a new one if not p.task_ids.filtered(lambda x: x.user_id == u) else # otherwise, return the task From 22aaa168c84ece2138fac625c9fa8f791afa2490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Pigeon?= Date: Wed, 21 Jun 2017 16:56:59 +0200 Subject: [PATCH 41/46] [10.0] web_widget_x2many_2d_matrix: update README --- web_widget_x2many_2d_matrix/README.rst | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 40098bff..d81d100c 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -40,7 +40,14 @@ This assumes that my_field refers to a model with the fields `x`, `y` and `value`. If your fields are named differently, pass the correct names as attributes:: - + + + + + + + + You can pass the following parameters: @@ -115,7 +122,14 @@ the field in the default function:: Now in our wizard, we can use:: - + + + + + + + + Note that all values in the matrix must exist, so you need to create them previously if not present, but you can control visually the editability of From 92c1f86066fb7fd2c4d5d0e2568bff0c34fe4aba Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Thu, 31 Aug 2017 01:03:06 +1000 Subject: [PATCH 42/46] [FIX] web_widget_x2many_2d_matrix: fixes (#712) * Patches to make module operational. * Minor fix to Readonly Switch * Fix to render to set change attribute. * Totals recompute. Fixes #697 --- web_widget_x2many_2d_matrix/__manifest__.py | 2 +- .../static/src/js/web_widget_x2many_2d_matrix.js | 8 ++++++-- .../static/src/xml/web_widget_x2many_2d_matrix.xml | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/web_widget_x2many_2d_matrix/__manifest__.py b/web_widget_x2many_2d_matrix/__manifest__.py index b4651c07..69b703cd 100644 --- a/web_widget_x2many_2d_matrix/__manifest__.py +++ b/web_widget_x2many_2d_matrix/__manifest__.py @@ -5,7 +5,7 @@ { "name": "2D matrix for x2many fields", - "version": "10.0.1.0.0", + "version": "10.0.1.0.1", "author": "Therp BV, " "Tecnativa," "Odoo Community Association (OCA)", diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index 43fa84bb..ec88f1ea 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -131,7 +131,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { }) .then(function() { - return self.dataset.read_ids(self.dataset.ids).then(function(rows) + return self.dataset.read_ids(self.dataset.ids, self.fields).then(function(rows) { // setup data structure _.each(rows, function(row) @@ -369,6 +369,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { $this.val(this.format_xy_value(value)); this.dataset.write($this.data('id'), data); + this.by_id[$this.data('id')][this.field_value] = value; $this.parent().removeClass('oe_form_invalid'); this.compute_totals(); } @@ -382,7 +383,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { effective_readonly_change: function() { this.$el - .find('tbody .read') + .find('tbody .edit') .toggle(!this.get('effective_readonly')); this.$el .find('tbody .read') @@ -404,6 +405,9 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { return $.when(result).then(function() { self.renderElement(); + self.compute_totals(); + self.$el.find('.edit').on( + 'change', self.proxy(self.xy_value_change)); }); }, }); diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml index a1a0d521..b7aaaefe 100644 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml @@ -16,8 +16,8 @@
@@ -14,9 +14,9 @@
+ - + - - + + From df0bca0bc51c48438ffb1580130b92d520f32bf9 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Wed, 17 Jan 2018 10:50:20 +0100 Subject: [PATCH 43/46] OCA Transbot updated translations from Transifex --- web_widget_x2many_2d_matrix/i18n/lt.po | 27 +++++++++++++++++++++++ web_widget_x2many_2d_matrix/i18n/nl_NL.po | 27 +++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 web_widget_x2many_2d_matrix/i18n/lt.po create mode 100644 web_widget_x2many_2d_matrix/i18n/nl_NL.po diff --git a/web_widget_x2many_2d_matrix/i18n/lt.po b/web_widget_x2many_2d_matrix/i18n/lt.po new file mode 100644 index 00000000..d9620d98 --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/lt.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Viktoras Norkus , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-01-25 01:58+0000\n" +"PO-Revision-Date: 2018-01-25 01:58+0000\n" +"Last-Translator: Viktoras Norkus , 2018\n" +"Language-Team: Lithuanian (https://www.transifex.com/oca/teams/23907/lt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: lt\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Suma" diff --git a/web_widget_x2many_2d_matrix/i18n/nl_NL.po b/web_widget_x2many_2d_matrix/i18n/nl_NL.po new file mode 100644 index 00000000..27efab7f --- /dev/null +++ b/web_widget_x2many_2d_matrix/i18n/nl_NL.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_x2many_2d_matrix +# +# Translators: +# Peter Hageman , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-01-03 03:50+0000\n" +"PO-Revision-Date: 2018-01-03 03:50+0000\n" +"Last-Translator: Peter Hageman , 2017\n" +"Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/teams/23907/nl_NL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: nl_NL\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_x2many_2d_matrix +#. openerp-web +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11 +#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28 +#, python-format +msgid "Total" +msgstr "Totaal" From 75f79755c0f391af25bf8ddf34490c533a01d656 Mon Sep 17 00:00:00 2001 From: Artem Kostyuk Date: Thu, 15 Feb 2018 09:45:16 +0200 Subject: [PATCH 44/46] [11][MIG] web_widget_x2many_2d_matrix WIP --- web_widget_x2many_2d_matrix/README.rst | 6 +- web_widget_x2many_2d_matrix/__init__.py | 1 - web_widget_x2many_2d_matrix/__manifest__.py | 7 +- web_widget_x2many_2d_matrix/i18n/lt.po | 4 +- web_widget_x2many_2d_matrix/i18n/nl_NL.po | 4 +- .../src/js/web_widget_x2many_2d_matrix.js | 65 ++++++++++++------- 6 files changed, 51 insertions(+), 36 deletions(-) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index d81d100c..6fb555b9 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -80,7 +80,7 @@ field_att_ .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/162/8.0 + :target: https://runbot.odoo-community.org/runbot/162/11.0 Example ======= @@ -104,6 +104,7 @@ the field in the default function:: users = self.env['res.users'].browse([1, 2, 3]) return [ (0, 0, { + 'name': 'Sample task name', 'project_id': p.id, 'user_id': u.id, 'planned_hours': 0, @@ -158,7 +159,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, -help us smashing it by providing a detailed and welcomed feedback. +help us smash it by providing a detailed and welcomed feedback. Credits ======= @@ -168,6 +169,7 @@ Contributors * Holger Brunn * Pedro M. Baeza +* Artem Kostyuk Maintainer ---------- diff --git a/web_widget_x2many_2d_matrix/__init__.py b/web_widget_x2many_2d_matrix/__init__.py index faef9dac..919541c6 100644 --- a/web_widget_x2many_2d_matrix/__init__.py +++ b/web_widget_x2many_2d_matrix/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution diff --git a/web_widget_x2many_2d_matrix/__manifest__.py b/web_widget_x2many_2d_matrix/__manifest__.py index 69b703cd..41f69a75 100644 --- a/web_widget_x2many_2d_matrix/__manifest__.py +++ b/web_widget_x2many_2d_matrix/__manifest__.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright 2015 Holger Brunn # Copyright 2016 Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - { "name": "2D matrix for x2many fields", - "version": "10.0.1.0.1", + "version": "11.0.1.0.0", "author": "Therp BV, " - "Tecnativa," + "Tecnativa, " "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/web", "license": "AGPL-3", "category": "Hidden/Dependency", "summary": "Show list fields as a matrix", diff --git a/web_widget_x2many_2d_matrix/i18n/lt.po b/web_widget_x2many_2d_matrix/i18n/lt.po index d9620d98..57a65fc5 100644 --- a/web_widget_x2many_2d_matrix/i18n/lt.po +++ b/web_widget_x2many_2d_matrix/i18n/lt.po @@ -6,10 +6,10 @@ # Viktoras Norkus , 2018 msgid "" msgstr "" -"Project-Id-Version: Odoo Server 10.0\n" +"Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-01-25 01:58+0000\n" -"PO-Revision-Date: 2018-01-25 01:58+0000\n" +"PO-Revision-Date: 2018-02-15 12:40+0200\n" "Last-Translator: Viktoras Norkus , 2018\n" "Language-Team: Lithuanian (https://www.transifex.com/oca/teams/23907/lt/)\n" "MIME-Version: 1.0\n" diff --git a/web_widget_x2many_2d_matrix/i18n/nl_NL.po b/web_widget_x2many_2d_matrix/i18n/nl_NL.po index 27efab7f..e1fde063 100644 --- a/web_widget_x2many_2d_matrix/i18n/nl_NL.po +++ b/web_widget_x2many_2d_matrix/i18n/nl_NL.po @@ -6,10 +6,10 @@ # Peter Hageman , 2017 msgid "" msgstr "" -"Project-Id-Version: Odoo Server 10.0\n" +"Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-01-03 03:50+0000\n" -"PO-Revision-Date: 2018-01-03 03:50+0000\n" +"PO-Revision-Date: 2018-02-15 12:39+0200\n" "Last-Translator: Peter Hageman , 2017\n" "Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/teams/23907/nl_NL/)\n" "MIME-Version: 1.0\n" diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js index ec88f1ea..2c0a0cd9 100644 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js @@ -6,13 +6,15 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { "use strict"; var core = require('web.core'); - var formats = require('web.formats'); - var FieldOne2Many = core.form_widget_registry.get('one2many'); - var Model = require('web.Model'); + var FieldManagerMixin = require('web.FieldManagerMixin'); + var Widget = require('web.Widget'); + var fieldRegistry = require('web.field_registry'); + var widgetRegistry = require('web.widget_registry'); + var widgetOne2many = widgetRegistry.get('one2many'); var data = require('web.data'); var $ = require('jquery'); - var WidgetX2Many2dMatrix = FieldOne2Many.extend({ + var WidgetX2Many2dMatrix = widgetOne2Many.extend(FieldManagerMixin, { template: 'FieldX2Many2dMatrix', widget_class: 'oe_form_field_x2many_2d_matrix', @@ -46,28 +48,39 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { }, // read parameters - init: function(field_manager, node) - { - this.field_x_axis = node.attrs.field_x_axis || this.field_x_axis; - this.field_y_axis = node.attrs.field_y_axis || this.field_y_axis; - this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis; - this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis; - this.x_axis_clickable = this.parse_boolean(node.attrs.x_axis_clickable || '1'); - this.y_axis_clickable = this.parse_boolean(node.attrs.y_axis_clickable || '1'); - this.field_value = node.attrs.field_value || this.field_value; - for (var property in node.attrs) { + init: function (parent, fieldname, record, therest) { + var res = this._super(parent, fieldname, record, therest); + FieldManagerMixin.init.call(this); + var node = record.fieldsInfo[therest.viewType][fieldname]; + + this.field_x_axis = node.field_x_axis || this.field_x_axis; + this.field_y_axis = node.field_y_axis || this.field_y_axis; + this.field_label_x_axis = node.field_label_x_axis || this.field_x_axis; + this.field_label_y_axis = node.field_label_y_axis || this.field_y_axis; + this.x_axis_clickable = this.parse_boolean(node.x_axis_clickable || '1'); + this.y_axis_clickable = this.parse_boolean(node.y_axis_clickable || '1'); + this.field_value = node.field_value || this.field_value; + for (var property in node) { if (property.startsWith("field_att_")) { - this.fields_att[property.substring(10)] = node.attrs[property]; + this.fields_att[property.substring(10)] = node[property]; } } - this.field_editability = node.attrs.field_editability || this.field_editability; - this.show_row_totals = this.parse_boolean(node.attrs.show_row_totals || '1'); - this.show_column_totals = this.parse_boolean(node.attrs.show_column_totals || '1'); - return this._super(field_manager, node); + this.field_editability = node.field_editability || this.field_editability; + this.show_row_totals = this.parse_boolean(node.show_row_totals || '1'); + this.show_column_totals = this.parse_boolean(node.show_column_totals || '1'); + this.init_fields(); + // this.set_value(undefined); + + return res; + }, + + init_fields: function() { + return; }, // return a field's value, id in case it's a one2many field get_field_value: function(row, field, many2one_as_name) + // FIXME looks silly { if(this.fields[field].type == 'many2one' && _.isArray(row[field])) { @@ -262,15 +275,13 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { // parse a value from user input parse_xy_value: function(val) { - return formats.parse_value( - val, {'type': this.fields[this.field_value].type}); + return val; }, // format a value from the database for display format_xy_value: function(val) { - return formats.format_value( - val, {'type': this.fields[this.field_value].type}); + return val; }, // compute totals @@ -412,7 +423,11 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { }, }); - core.form_widget_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix); + fieldRegistry.add( + 'x2many_2d_matrix', WidgetX2Many2dMatrix + ); - return WidgetX2Many2dMatrix; + return { + WidgetX2Many2dMatrix: WidgetX2Many2dMatrix + }; }); From 69cc921ab36e4dee42c68d749f3ce259bc5b5e5f Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 19 Feb 2018 17:48:06 +0100 Subject: [PATCH 45/46] [MIG+REF][11] web_widget_x2many_2d_matrix The widget has been completely refactored to benefit from the new MVC paradigm introduced in v11. --- .../odoo/addons/web_widget_x2many_2d_matrix | 1 + setup/web_widget_x2many_2d_matrix/setup.cfg | 2 + setup/web_widget_x2many_2d_matrix/setup.py | 6 + web_widget_x2many_2d_matrix/README.rst | 53 +-- web_widget_x2many_2d_matrix/__manifest__.py | 7 +- .../static/description/icon.png | Bin 5139 -> 2477 bytes .../static/description/screenshot.png | Bin 19577 -> 22639 bytes .../src/css/web_widget_x2many_2d_matrix.css | 9 +- .../static/src/js/2d_matrix_renderer.js | 416 +++++++++++++++++ .../src/js/web_widget_x2many_2d_matrix.js | 433 ------------------ .../static/src/js/widget_x2many_2d_matrix.js | 172 +++++++ .../src/xml/web_widget_x2many_2d_matrix.xml | 36 -- .../views/{templates.xml => assets.xml} | 3 +- 13 files changed, 623 insertions(+), 515 deletions(-) create mode 120000 setup/web_widget_x2many_2d_matrix/odoo/addons/web_widget_x2many_2d_matrix create mode 100644 setup/web_widget_x2many_2d_matrix/setup.cfg create mode 100644 setup/web_widget_x2many_2d_matrix/setup.py create mode 100644 web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js delete mode 100644 web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js create mode 100644 web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js delete mode 100644 web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml rename web_widget_x2many_2d_matrix/views/{templates.xml => assets.xml} (72%) diff --git a/setup/web_widget_x2many_2d_matrix/odoo/addons/web_widget_x2many_2d_matrix b/setup/web_widget_x2many_2d_matrix/odoo/addons/web_widget_x2many_2d_matrix new file mode 120000 index 00000000..4d06f547 --- /dev/null +++ b/setup/web_widget_x2many_2d_matrix/odoo/addons/web_widget_x2many_2d_matrix @@ -0,0 +1 @@ +../../../../web_widget_x2many_2d_matrix \ No newline at end of file diff --git a/setup/web_widget_x2many_2d_matrix/setup.cfg b/setup/web_widget_x2many_2d_matrix/setup.cfg new file mode 100644 index 00000000..3c6e79cf --- /dev/null +++ b/setup/web_widget_x2many_2d_matrix/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 diff --git a/setup/web_widget_x2many_2d_matrix/setup.py b/setup/web_widget_x2many_2d_matrix/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/web_widget_x2many_2d_matrix/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 6fb555b9..52eb81b1 100644 --- a/web_widget_x2many_2d_matrix/README.rst +++ b/web_widget_x2many_2d_matrix/README.rst @@ -9,12 +9,13 @@ This module allows to show an x2many field with 3-tuples ($x_value, $y_value, $value) in a table -========= =========== =========== -\ $x_value1 $x_value2 -========= =========== =========== -$y_value1 $value(1/1) $value(2/1) -$y_value2 $value(1/2) $value(2/2) -========= =========== =========== ++-----------+-------------+-------------+ +| | $x_value1 | $x_value2 | ++===========+=============+=============+ +| $y_value1 | $value(1/1) | $value(2/1) | ++-----------+-------------+-------------+ +| $y_value2 | $value(1/2) | $value(2/2) | ++-----------+-------------+-------------+ where `value(n/n)` is editable. @@ -59,12 +60,6 @@ field_label_x_axis Use another field to display in the table header field_label_y_axis Use another field to display in the table header -x_axis_clickable - It indicates if the X axis allows to be clicked for navigating to the field - (if it's a many2one field). True by default -y_axis_clickable - It indicates if the Y axis allows to be clicked for navigating to the field - (if it's a many2one field). True by default field_value Show this field as value show_row_totals @@ -73,10 +68,6 @@ show_row_totals show_column_totals If field_value is a numeric field, it indicates if you want to calculate column totals. True by default -field_att_ - Declare as many options prefixed with this string as you need for binding - a field value with an HTML node attribute (disabled, class, style...) - called as the `` passed in the option. .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot @@ -92,7 +83,7 @@ data model and point to it from our wizard. The crucial part is that we fill the field in the default function:: from odoo import fields, models - + class MyWizard(models.TransientModel): _name = 'my.wizard' @@ -105,8 +96,8 @@ the field in the default function:: return [ (0, 0, { 'name': 'Sample task name', - 'project_id': p.id, - 'user_id': u.id, + 'project_id': p.id, + 'user_id': u.id, 'planned_hours': 0, 'message_needaction': False, 'date_deadline': fields.Date.today(), @@ -132,26 +123,17 @@ Now in our wizard, we can use:: -Note that all values in the matrix must exist, so you need to create them -previously if not present, but you can control visually the editability of -the fields in the matrix through `field_att_disabled` option with a control -field. Known issues / Roadmap ====================== -* It would be worth trying to instantiate the proper field widget and let it render the input -* Let the widget deal with the missing values of the full Cartesian product, - instead of being forced to pre-fill all the possible values. -* If you pass values with an onchange, you need to overwrite the model's method - `onchange` for making the widget work:: +* Support extra attributes on each field cell via `field_extra_attrs` param. + We could set a cell as not editable, required or readonly for instance. + The `readonly` case will also give the ability + to click on m2o to open related records. + +* Support limit total records in the matrix. Ref: https://github.com/OCA/web/issues/901 - @api.multi - def onchange(self, values, field_name, field_onchange): - if "one2many_field" in field_onchange: - for sub in []: - field_onchange.setdefault("one2many_field." + sub, u"") - return super(model, self).onchange(values, field_name, field_onchange) Bug Tracker =========== @@ -170,6 +152,9 @@ Contributors * Holger Brunn * Pedro M. Baeza * Artem Kostyuk +* Simone Orsi +* Timon Tschanz + Maintainer ---------- diff --git a/web_widget_x2many_2d_matrix/__manifest__.py b/web_widget_x2many_2d_matrix/__manifest__.py index 41f69a75..31fa2d5a 100644 --- a/web_widget_x2many_2d_matrix/__manifest__.py +++ b/web_widget_x2many_2d_matrix/__manifest__.py @@ -1,11 +1,13 @@ # Copyright 2015 Holger Brunn # Copyright 2016 Pedro M. Baeza +# Copyright 2018 Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "2D matrix for x2many fields", "version": "11.0.1.0.0", "author": "Therp BV, " "Tecnativa, " + "Camptocamp, " "Odoo Community Association (OCA)", "website": "https://github.com/OCA/web", "license": "AGPL-3", @@ -15,10 +17,7 @@ 'web', ], "data": [ - 'views/templates.xml', - ], - "qweb": [ - 'static/src/xml/web_widget_x2many_2d_matrix.xml', + 'views/assets.xml', ], "installable": True, } diff --git a/web_widget_x2many_2d_matrix/static/description/icon.png b/web_widget_x2many_2d_matrix/static/description/icon.png index d7cdcec3b4f3db5e2af2745392b116e16a2e40b4..a501fbf835ea6ee937588af1c15a104e6a97bf27 100644 GIT binary patch literal 2477 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 diff --git a/web_widget_x2many_2d_matrix/static/description/screenshot.png b/web_widget_x2many_2d_matrix/static/description/screenshot.png index 47c2a40d62b23f3b5c4b97dbc699f20dc5baa5ba..922e2961351acf706cef3946526403daa48108ee 100644 GIT binary patch literal 22639 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

q$zofJg&#hQSr2BbXSC%$N*Ni65_QQ{ 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 tbody > tr > td.oe_list_field_cell -{ - white-space: normal; +.o_field_x2many_2d_matrix .row-total { + font-weight: bold; } diff --git a/web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js b/web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js new file mode 100644 index 00000000..898ac0d5 --- /dev/null +++ b/web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js @@ -0,0 +1,416 @@ +/* Copyright 2018 Simone Orsi + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ + +odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (require) { + "use strict"; + + // heavily inspired by Odoo's `ListRenderer` + var BasicRenderer = require('web.BasicRenderer'); + var config = require('web.config'); + var field_utils = require('web.field_utils'); + var utils = require('web.utils'); + var FIELD_CLASSES = { + // copied from ListRenderer + float: 'o_list_number', + integer: 'o_list_number', + monetary: 'o_list_number', + text: 'o_list_text', + }; + + var X2Many2dMatrixRenderer = BasicRenderer.extend({ + + init: function (parent, state, params) { + this._super.apply(this, arguments); + this.editable = params.editable; + this.columns = params.matrix_data.columns; + this.rows = params.matrix_data.rows; + this.matrix_data = params.matrix_data; + }, + /** + * Main render function for the matrix widget. It is rendered as a table. For now, + * this method does not wait for the field widgets to be ready. + * + * @override + * @private + * returns {Deferred} this deferred is resolved immediately + */ + _renderView: function () { + var self = this; + + this.$el + .removeClass('table-responsive') + .empty(); + + var $table = $('').addClass('o_list_view table table-condensed table-striped'); + this.$el + .addClass('table-responsive') + .append($table); + + this._computeColumnAggregates(); + this._computeRowAggregates(); + + $table + .append(this._renderHeader()) + .append(this._renderBody()); + if (self.matrix_data.show_column_totals) { + $table.append(this._renderFooter()); + } + return this._super(); + }, + /** + * Render the table body. Looks for the table body and renders the rows in it. + * Also it sets the tabindex on every input element. + * + * @private + * return {jQueryElement} The table body element that was just filled. + */ + _renderBody: function () { + var $body = $('').append(this._renderRows()); + _.each($body.find('input'), function (td, i) { + $(td).attr('tabindex', i); + }); + return $body; + }, + /** + * Render the table head of our matrix. Looks for the first table head + * and inserts the header into it. + * + * @private + * @return {jQueryElement} The thead element that was inserted into. + */ + _renderHeader: function () { + var $tr = $('').append('').append($tr); + }, + /** + * Render a single header cell. Creates a th and adds the description as text. + * + * @private + * @param {jQueryElement} node + * @returns {jQueryElement} the created . + * If aggregate is set on the row it also will generate the aggregate cell. + * + * @private + * @param {Object} row: The row that will be rendered. + * @returns {jQueryElement} the element that has been rendered. + */ + _renderRow: function (row) { + var self = this; + var $tr = $('', {class: 'o_data_row'}); + $tr = $tr.append(self._renderLabelCell(row.data[0])); + var $cells = _.map(this.columns, function (node, index) { + var record = row.data[index]; + // make the widget use our field value for each cell + node.attrs.name = self.matrix_data.field_value; + return self._renderBodyCell(record, node, index, {mode:''}); + }); + $tr = $tr.append($cells); + if (row.aggregate) { + $tr.append(self._renderAggregateRowCell(row)); + } + return $tr; + }, + /** + * Renders the label for a specific row. + * + * @private + * @params {Object} record: Contains the information about the record. + * @params {jQueryElement} the cell that was rendered. + */ + _renderLabelCell: function(record) { + var $td = $('').append($('').append('
'); + $tr= $tr.append(_.map(this.columns, this._renderHeaderCell.bind(this))); + if (this.matrix_data.show_row_totals) { + $tr.append($('', {class: 'total'})); + } + return $('
node. + */ + _renderHeaderCell: function (node) { + var name = node.attrs.name; + var field = this.state.fields[name]; + var $th = $(''); + if (!field) { + return $th; + } + var description; + if (node.attrs.widget) { + description = this.state.fieldsInfo.list[name].Widget.prototype.description; + } + if (description === undefined) { + description = node.attrs.string || field.string; + } + $th.text(description).data('name', name); + + if (field.type === 'float' || field.type === 'integer' || field.type === 'monetary') { + $th.addClass('text-right'); + } + + if (config.debug) { + var fieldDescr = { + field: field, + name: name, + string: description || name, + record: this.state, + attrs: node.attrs, + }; + this._addFieldTooltip(fieldDescr, $th); + } + return $th; + }, + /** + * Proxy call to function rendering single row. + * + * @private + * @returns {String} a string with the generated html. + * + */ + + _renderRows: function () { + return _.map(this.rows, this._renderRow.bind(this)); + }, + /** + * Render a single row with all its columns. Renders all the cells and then wraps them with a
'); + var value = record.data[this.matrix_data.field_y_axis]; + if (value.type == 'record') { + // we have a related record + value = value.data.display_name; + } + // get 1st column filled w/ Y label + $td.text(value); + return $td; + }, + /** + * Create a cell and fill it with the aggregate value. + * + * @private + * @param {Object} row: the row object to aggregate. + * @returns {jQueryElement} The rendered cell. + */ + _renderAggregateRowCell: function (row) { + var $cell = $('', {class: 'row-total text-right'}); + this._apply_aggregate_value($cell, row.aggregate); + return $cell; + }, + /** + * Render a single body Cell. + * Gets the field and renders the widget. We force the edit mode, since + * we always want the widget to be editable. + * + * @private + * @param {Object} record: Contains the data for this cell + * @param {jQueryElement} node: The HTML of the field. + * @param {int} colIndex: The index of the current column. + * @param {Object} options: The obtions used for the widget + * @returns {jQueryElement} the rendered cell. + */ + _renderBodyCell: function (record, node, colIndex, options) { + var tdClassName = 'o_data_cell'; + if (node.tag === 'button') { + tdClassName += ' o_list_button'; + } else if (node.tag === 'field') { + var typeClass = FIELD_CLASSES[this.state.fields[node.attrs.name].type]; + if (typeClass) { + tdClassName += (' ' + typeClass); + } + if (node.attrs.widget) { + tdClassName += (' o_' + node.attrs.widget + '_cell'); + } + } + // TODO roadmap: here we should collect possible extra params + // the user might want to attach to each single cell. + var $td = $('', { + 'class': tdClassName, + 'data-form-id': record.id, + 'data-id': record.data.id, + }); + // We register modifiers on the element so that it gets the correct + // modifiers classes (for styling) + var modifiers = this._registerModifiers(node, record, $td, _.pick(options, 'mode')); + // If the invisible modifiers is true, the element is left empty. + // Indeed, if the modifiers was to change the whole cell would be + // rerendered anyway. + if (modifiers.invisible && !(options && options.renderInvisible)) { + return $td; + } + options.mode = 'edit'; // enforce edit mode + var widget = this._renderFieldWidget(node, record, _.pick(options, 'mode')); + this._handleAttributes(widget.$el, node); + return $td.append(widget.$el); + }, + /** + * Wraps the column aggregate with a tfoot element + * + * @private + * @returns {jQueryElement} The footer element with the cells in it. + */ + _renderFooter: function () { + var $cells = this._renderAggregateColCells(); + if ($cells) { + return $('
').append($cells)); + } + return; + }, + /** + * Render the Aggregate cells for the column. + * + * @private + * @returns {List} the rendered cells + */ + _renderAggregateColCells: function () { + var self = this; + return _.map(this.columns, function (column, index) { + var $cell = $('', {class: 'col-total text-right'}); + if (column.aggregate) { + self._apply_aggregate_value($cell, column.aggregate); + } + return $cell; + }); + }, + /** + * Compute the column aggregates. + * This function is called everytime the value is changed. + * + * @private + */ + _computeColumnAggregates: function () { + if (!this.matrix_data.show_column_totals) { + return; + } + var self = this, + fname = this.matrix_data.field_value, + field = this.state.fields[fname]; + if (!field) { return; } + var type = field.type; + if (type !== 'integer' && type !== 'float' && type !== 'monetary') { + return; + } + _.each(self.columns, function (column, index) { + column.aggregate = { + fname: fname, + ftype: type, + // TODO: translate + help: 'Sum', + value: 0 + }; + _.each(self.rows, function (row) { + // var record = _.findWhere(self.state.data, {id: col.data.id}); + column.aggregate.value += row.data[index].data[fname]; + }); + }); + }, + /** + * Compute the row aggregates. + * This function is called everytime the value is changed. + * + * @private + */ + _computeRowAggregates: function () { + if (!this.matrix_data.show_row_totals) { + return; + } + var self = this, + fname = this.matrix_data.field_value, + field = this.state.fields[fname]; + if (!field) { return; } + var type = field.type; + if (type !== 'integer' && type !== 'float' && type !== 'monetary') { + return; + } + _.each(self.rows, function (row) { + row.aggregate = { + fname: fname, + ftype: type, + // TODO: translate + help: 'Sum', + value: 0 + }; + _.each(row.data, function (col) { + row.aggregate.value += col.data[fname]; + }); + }); + }, + /** + * Takes the given Value, formats it and adds it to the given cell. + * + * @private + * @param {jQueryElement} $cell: The Cell where the aggregate should be added. + * @param {Object} aggregate: The object which contains the information about the aggregate value + */ + _apply_aggregate_value: function ($cell, aggregate) { + var field = this.state.fields[aggregate.fname], + formatter = field_utils.format[field.type]; + var formattedValue = formatter(aggregate.value, field, {escape: true, }); + $cell.addClass('total').attr('title', aggregate.help).html(formattedValue); + }, + /** + * Check if the change was successful and then update the grid. + * This function is required on relational fields. + * + * @params {Object} state: Contains the current state of the field & all the data + * @params {String} id: the id of the updated object. + * @params {Array} fields: The fields we have in the view. + * @params {Object} ev: The event object. + * @returns {Deferred} The deferred object thats gonna be resolved when the change is made. + */ + confirmUpdate: function (state, id, fields, ev) { + var self = this; + this.state = state; + return this.confirmChange(state, id, fields, ev).then(function () { + self._refresh(id); + }); + }, + /** + * Refresh our grid. + * + * @private + */ + _refresh: function (id) { + this._updateRow(id); + this._refreshColTotals(); + this._refreshRowTotals(); + }, + /** + *Update row data in our internal rows. + * + * @params {String} id: The id of the row that needs to be updated. + */ + _updateRow: function (id) { + var self = this, + record = _.findWhere(self.state.data, {id: id}); + _.each(self.rows, function(row) { + _.each(row.data, function(col, i) { + if (col.id == id) { + row.data[i] = record; + } + }); + }); + }, + /** + * Update the row total. + */ + _refreshColTotals: function () { + this._computeColumnAggregates(); + this.$('tfoot').replaceWith(this._renderFooter()); + }, + /** + * Update the column total. + */ + _refreshRowTotals: function () { + var self = this; + this._computeRowAggregates(); + var $rows = self.$el.find('tr.o_data_row'); + _.each(self.rows, function(row, i) { + if (row.aggregate) { + $($rows[i]).find('.row-total') + .replaceWith(self._renderAggregateRowCell(row)); + } + }); + }, + /* + x2m fields expect this + */ + getEditableRecordID: function (){ return false;} + + }); + + return X2Many2dMatrixRenderer; +}); diff --git a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js deleted file mode 100644 index 2c0a0cd9..00000000 --- a/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js +++ /dev/null @@ -1,433 +0,0 @@ -/* Copyright 2015 Holger Brunn - * Copyright 2016 Pedro M. Baeza - * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ - -odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { - "use strict"; - - var core = require('web.core'); - var FieldManagerMixin = require('web.FieldManagerMixin'); - var Widget = require('web.Widget'); - var fieldRegistry = require('web.field_registry'); - var widgetRegistry = require('web.widget_registry'); - var widgetOne2many = widgetRegistry.get('one2many'); - var data = require('web.data'); - var $ = require('jquery'); - - var WidgetX2Many2dMatrix = widgetOne2Many.extend(FieldManagerMixin, { - template: 'FieldX2Many2dMatrix', - widget_class: 'oe_form_field_x2many_2d_matrix', - - // those will be filled with rows from the dataset - by_x_axis: {}, - by_y_axis: {}, - by_id: {}, - // configuration values - field_x_axis: 'x', - field_label_x_axis: 'x', - field_y_axis: 'y', - field_label_y_axis: 'y', - field_value: 'value', - x_axis_clickable: true, - y_axis_clickable: true, - // information about our datatype - is_numeric: false, - show_row_totals: true, - show_column_totals: true, - // this will be filled with the model's fields_get - fields: {}, - // Store fields used to fill HTML attributes - fields_att: {}, - - parse_boolean: function(val) - { - if (val.toLowerCase() === 'true' || val === '1') { - return true; - } - return false; - }, - - // read parameters - init: function (parent, fieldname, record, therest) { - var res = this._super(parent, fieldname, record, therest); - FieldManagerMixin.init.call(this); - var node = record.fieldsInfo[therest.viewType][fieldname]; - - this.field_x_axis = node.field_x_axis || this.field_x_axis; - this.field_y_axis = node.field_y_axis || this.field_y_axis; - this.field_label_x_axis = node.field_label_x_axis || this.field_x_axis; - this.field_label_y_axis = node.field_label_y_axis || this.field_y_axis; - this.x_axis_clickable = this.parse_boolean(node.x_axis_clickable || '1'); - this.y_axis_clickable = this.parse_boolean(node.y_axis_clickable || '1'); - this.field_value = node.field_value || this.field_value; - for (var property in node) { - if (property.startsWith("field_att_")) { - this.fields_att[property.substring(10)] = node[property]; - } - } - this.field_editability = node.field_editability || this.field_editability; - this.show_row_totals = this.parse_boolean(node.show_row_totals || '1'); - this.show_column_totals = this.parse_boolean(node.show_column_totals || '1'); - this.init_fields(); - // this.set_value(undefined); - - return res; - }, - - init_fields: function() { - return; - }, - - // return a field's value, id in case it's a one2many field - get_field_value: function(row, field, many2one_as_name) - // FIXME looks silly - { - if(this.fields[field].type == 'many2one' && _.isArray(row[field])) - { - if(many2one_as_name) - { - return row[field][1]; - } - else - { - return row[field][0]; - } - } - return row[field]; - }, - - // setup our datastructure for simple access in the template - set_value: function(value_) - { - var self = this, - result = this._super(value_); - - self.by_x_axis = {}; - self.by_y_axis = {}; - self.by_id = {}; - - return $.when(result).then(function() - { - return self.dataset._model.call('fields_get').then(function(fields) - { - self.fields = fields; - self.is_numeric = fields[self.field_value].type == 'float'; - self.show_row_totals &= self.is_numeric; - self.show_column_totals &= self.is_numeric; - }) - // if there are cached writes on the parent dataset, read below - // only returns the written data, which is not enough to properly - // set up our data structure. Read those ids here and patch the - // cache - .then(function() - { - var ids_written = _.map( - self.dataset.to_write, function(x) { return x.id }); - if(!ids_written.length) - { - return; - } - return (new data.Query(self.dataset._model)) - .filter([['id', 'in', ids_written]]) - .all() - .then(function(rows) - { - _.each(rows, function(row) - { - var cache = _.find( - self.dataset.cache, - function(x) { return x.id == row.id } - ); - _.extend(cache.values, row, _.clone(cache.values)); - }) - }) - }) - .then(function() - { - return self.dataset.read_ids(self.dataset.ids, self.fields).then(function(rows) - { - // setup data structure - _.each(rows, function(row) - { - self.add_xy_row(row); - }); - if(self.is_started && !self.no_rerender) - { - self.renderElement(); - self.compute_totals(); - self.setup_many2one_axes(); - self.$el.find('.edit').on( - 'change', self.proxy(self.xy_value_change)); - self.effective_readonly_change(); - } - }); - }); - }); - }, - - // do whatever needed to setup internal data structure - add_xy_row: function(row) - { - var x = this.get_field_value(row, this.field_x_axis), - y = this.get_field_value(row, this.field_y_axis); - // row is a *copy* of a row in dataset.cache, fetch - // a reference to this row in order to have the - // internal data structure point to the same data - // the dataset manipulates - _.every(this.dataset.cache, function(cached_row) - { - if(cached_row.id == row.id) - { - row = cached_row.values; - // new rows don't have that - row.id = cached_row.id; - return false; - } - return true; - }); - this.by_x_axis[x] = this.by_x_axis[x] || {}; - this.by_y_axis[y] = this.by_y_axis[y] || {}; - this.by_x_axis[x][y] = row; - this.by_y_axis[y][x] = row; - this.by_id[row.id] = row; - }, - - // get x axis values in the correct order - get_x_axis_values: function() - { - return _.keys(this.by_x_axis); - }, - - // get y axis values in the correct order - get_y_axis_values: function() - { - return _.keys(this.by_y_axis); - }, - - // get the label for a value on the x axis - get_x_axis_label: function(x) - { - return this.get_field_value( - _.first(_.values(this.by_x_axis[x])), - this.field_label_x_axis, true); - }, - - // get the label for a value on the y axis - get_y_axis_label: function(y) - { - return this.get_field_value( - _.first(_.values(this.by_y_axis[y])), - this.field_label_y_axis, true); - }, - - // return the class(es) the inputs should have - get_xy_value_class: function() - { - var classes = 'oe_form_field oe_form_required'; - if(this.is_numeric) - { - classes += ' oe_form_field_float'; - } - return classes; - }, - - // return row id of a coordinate - get_xy_id: function(x, y) - { - return this.by_x_axis[x][y]['id']; - }, - - get_xy_att: function(x, y) - { - var vals = {}; - for (var att in this.fields_att) { - var val = this.get_field_value( - this.by_x_axis[x][y], this.fields_att[att]); - // Discard empty values - if (val) { - vals[att] = val; - } - } - return vals; - }, - - // return the value of a coordinate - get_xy_value: function(x, y) - { - return this.get_field_value( - this.by_x_axis[x][y], this.field_value); - }, - - // validate a value - validate_xy_value: function(val) - { - try - { - this.parse_xy_value(val); - } - catch(e) - { - return false; - } - return true; - }, - - // parse a value from user input - parse_xy_value: function(val) - { - return val; - }, - - // format a value from the database for display - format_xy_value: function(val) - { - return val; - }, - - // compute totals - compute_totals: function() - { - var self = this, - grand_total = 0, - totals_x = {}, - totals_y = {}, - rows = this.by_id, - deferred = $.Deferred(); - _.each(rows, function(row) - { - var key_x = self.get_field_value(row, self.field_x_axis), - key_y = self.get_field_value(row, self.field_y_axis); - totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value); - totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value); - grand_total += self.get_field_value(row, self.field_value); - }); - _.each(totals_y, function(total, y) - { - self.$el.find( - _.str.sprintf('td.row_total[data-y="%s"]', y)).text( - self.format_xy_value(total)); - }); - _.each(totals_x, function(total, x) - { - self.$el.find( - _.str.sprintf('td.column_total[data-x="%s"]', x)).text( - self.format_xy_value(total)); - }); - self.$el.find('.grand_total').text( - self.format_xy_value(grand_total)) - deferred.resolve({ - totals_x: totals_x, - totals_y: totals_y, - grand_total: grand_total, - rows: rows, - }); - return deferred; - }, - - setup_many2one_axes: function() - { - if(this.fields[this.field_x_axis].type == 'many2one' && this.x_axis_clickable) - { - this.$el.find('th[data-x]').addClass('oe_link') - .click(_.partial( - this.proxy(this.many2one_axis_click), - this.field_x_axis, 'x')); - } - if(this.fields[this.field_y_axis].type == 'many2one' && this.y_axis_clickable) - { - this.$el.find('tr[data-y] th').addClass('oe_link') - .click(_.partial( - this.proxy(this.many2one_axis_click), - this.field_y_axis, 'y')); - } - }, - - many2one_axis_click: function(field, id_attribute, e) - { - this.do_action({ - type: 'ir.actions.act_window', - name: this.fields[field].string, - res_model: this.fields[field].relation, - res_id: $(e.currentTarget).data(id_attribute), - views: [[false, 'form']], - target: 'current', - }) - }, - - start: function() - { - var self = this; - this.$el.find('.edit').on( - 'change', self.proxy(this.xy_value_change)); - this.compute_totals(); - this.setup_many2one_axes(); - this.on("change:effective_readonly", - this, this.proxy(this.effective_readonly_change)); - this.effective_readonly_change(); - return this._super(); - }, - - xy_value_change: function(e) - { - var $this = $(e.currentTarget), - val = $this.val(); - if(this.validate_xy_value(val)) - { - var data = {}, value = this.parse_xy_value(val); - data[this.field_value] = value; - - $this.siblings('.read').text(this.format_xy_value(value)); - $this.val(this.format_xy_value(value)); - - this.dataset.write($this.data('id'), data); - this.by_id[$this.data('id')][this.field_value] = value; - $this.parent().removeClass('oe_form_invalid'); - this.compute_totals(); - } - else - { - $this.parent().addClass('oe_form_invalid'); - } - - }, - - effective_readonly_change: function() - { - this.$el - .find('tbody .edit') - .toggle(!this.get('effective_readonly')); - this.$el - .find('tbody .read') - .toggle(this.get('effective_readonly')); - this.$el.find('.edit').first().focus(); - }, - - is_syntax_valid: function() - { - return this.$el.find('.oe_form_invalid').length == 0; - }, - - load_views: function() { - // Needed for removing the initial empty tree view when the widget - // is loaded - var self = this, - result = this._super(); - - return $.when(result).then(function() - { - self.renderElement(); - self.compute_totals(); - self.$el.find('.edit').on( - 'change', self.proxy(self.xy_value_change)); - }); - }, - }); - - fieldRegistry.add( - 'x2many_2d_matrix', WidgetX2Many2dMatrix - ); - - return { - WidgetX2Many2dMatrix: WidgetX2Many2dMatrix - }; -}); diff --git a/web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js new file mode 100644 index 00000000..4b1a73f9 --- /dev/null +++ b/web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js @@ -0,0 +1,172 @@ +/* Copyright 2015 Holger Brunn + * Copyright 2016 Pedro M. Baeza + * Copyright 2018 Simone Orsi + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ + +odoo.define('web_widget_x2many_2d_matrix.widget', function (require) { + "use strict"; + + var core = require('web.core'); + // var FieldManagerMixin = require('web.FieldManagerMixin'); + var field_registry = require('web.field_registry'); + var relational_fields = require('web.relational_fields'); + var weContext = require('web_editor.context'); + // var Helpers = require('web_widget_x2many_2d_matrix.helpers'); + var AbstractField = require('web.AbstractField'); + var X2Many2dMatrixRenderer = require('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer'); + + var WidgetX2Many2dMatrix = relational_fields.FieldOne2Many.extend({ + widget_class: 'o_form_field_x2many_2d_matrix', + /** + * Initialize the widget & parameters. + * + * @param {Object} parent: contains the form view. + * @param {String} name: the name of the field. + * @param {Object} record: Contains the information about the database records. + * @param {Object} options: Contains the view options. + */ + init: function (parent, name, record, options) { + this._super(parent, name, record, options); + this.init_params(); + }, + + /** + * Initialize the widget specific parameters. + * Sets the axis and the values. + */ + init_params: function () { + var node = this.attrs; + this.by_x_axis = {}; + this.by_y_axis = {}; + this.field_x_axis = node.field_x_axis || this.field_x_axis; + this.field_y_axis = node.field_y_axis || this.field_y_axis; + this.field_label_x_axis = node.field_label_x_axis || this.field_x_axis; + this.field_label_y_axis = node.field_label_y_axis || this.field_y_axis; + this.x_axis_clickable = this.parse_boolean(node.x_axis_clickable || '1'); + this.y_axis_clickable = this.parse_boolean(node.y_axis_clickable || '1'); + this.field_value = node.field_value || this.field_value; + // TODO: is this really needed? Holger? + for (var property in node) { + if (property.startsWith("field_att_")) { + this.fields_att[property.substring(10)] = node[property]; + } + } + // and this? + this.field_editability = node.field_editability || this.field_editability; + this.show_row_totals = this.parse_boolean(node.show_row_totals || '1'); + this.show_column_totals = this.parse_boolean(node.show_column_totals || '1'); + this.init_matrix(); + }, + /** + * Initializes the Value matrix. + * Puts the values in the grid. If we have related items we use the display name. + */ + init_matrix: function(){ + var self = this, + records = self.recordData[this.name].data; + _.each(records, function(record) { + var x = record.data[self.field_x_axis], + y = record.data[self.field_y_axis]; + if (x.type == 'record') { + // we have a related record + x = x.data.display_name; + } + if (y.type == 'record') { + // we have a related record + y = y.data.display_name; + } + self.by_x_axis[x] = self.by_x_axis[x] || {}; + self.by_y_axis[y] = self.by_y_axis[y] || {}; + self.by_x_axis[x][y] = record; + self.by_y_axis[y][x] = record; + }); + // init columns + self.columns = []; + $.each(self.by_x_axis, function(x){ + self.columns.push(self._make_column(x)); + }); + self.rows = []; + $.each(self.by_y_axis, function(y){ + self.rows.push(self._make_row(y)); + }); + self.matrix_data = { + 'field_value': self.field_value, + 'field_x_axis': self.field_x_axis, + 'field_y_axis': self.field_y_axis, + 'columns': self.columns, + 'rows': self.rows, + 'show_row_totals': self.show_row_totals, + 'show_column_totals': self.show_column_totals + }; + + }, + /** + * Create scaffold for a column. + * + * @params {String} x: The string used as a column title + */ + _make_column: function(x){ + return { + // simulate node parsed on xml arch + 'tag': 'field', + 'attrs': { + 'name': this.field_x_axis, + 'string': x + } + }; + }, + /** + * Create scaffold for a row. + * + * @params {String} x: The string used as a row title + */ + _make_row: function(y){ + var self = this; + // use object so that we can attach more data if needed + var row = {'data': []}; + $.each(self.by_x_axis, function(x) { + row.data.push(self.by_y_axis[y][x]); + }); + return row; + }, + /** + *Parse a String containing a Python bool or 1 and convert it to a proper bool. + * + * @params {String} val: the string to be parsed. + * @returns {Boolean} The parsed boolean. + */ + parse_boolean: function(val) { + if (val.toLowerCase() === 'true' || val === '1') { + return true; + } + return false; + }, + /** + *Create the matrix renderer and add its output to our element + * + * @returns {Deferred} A deferred object to be completed when it finished rendering. + */ + _render: function () { + if (!this.view) { + return this._super(); + } + var arch = this.view.arch, + viewType = 'list'; + this.renderer = new X2Many2dMatrixRenderer(this, this.value, { + arch: arch, + editable: true, + viewType: viewType, + matrix_data: this.matrix_data + }); + this.$el.addClass('o_field_x2many o_field_x2many_2d_matrix'); + return this.renderer.appendTo(this.$el); + } + + }); + + field_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix); + + return { + WidgetX2Many2dMatrix: WidgetX2Many2dMatrix + }; +}); diff --git a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml b/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml deleted file mode 100644 index b7aaaefe..00000000 --- a/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml +++ /dev/null @@ -1,36 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - -
- - - Total
- - - - - -
Total -
-
-
-
diff --git a/web_widget_x2many_2d_matrix/views/templates.xml b/web_widget_x2many_2d_matrix/views/assets.xml similarity index 72% rename from web_widget_x2many_2d_matrix/views/templates.xml rename to web_widget_x2many_2d_matrix/views/assets.xml index 06934cc3..ba820435 100644 --- a/web_widget_x2many_2d_matrix/views/templates.xml +++ b/web_widget_x2many_2d_matrix/views/assets.xml @@ -3,7 +3,8 @@