From 9d536996e8966b16f657fcbde7dbd285145af9f9 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 11 Mar 2015 18:00:21 +0100 Subject: [PATCH 01/58] [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 000000000000..27a266b981d6 --- /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 000000000000..faef9dac007f --- /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 000000000000..1cbc4aad73cb --- /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 000000000000..e29367a010ae --- /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 000000000000..06934cc33dbc --- /dev/null +++ b/web_widget_x2many_2d_matrix/views/templates.xml @@ -0,0 +1,11 @@ + + + + + + From 4e73cdd1d749b2a8f0ff703f431b77868f4cee02 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 11 Mar 2015 18:05:40 +0100 Subject: [PATCH 02/58] [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 27a266b981d6..6be504c44bf3 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 3ec242af300c1d8dad0ac7427a5f2e25deeeaa91 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 12:22:37 +0100 Subject: [PATCH 03/58] [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 e29367a010ae..fe3f82d31c4c 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 1aa706dfe109d634e9e0ed0f3ec157c67f606199 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 12:27:24 +0100 Subject: [PATCH 04/58] [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 3a754f42651d..b157a67994ea 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 4ef233da8ee82e96208b7ee45ea1bb52f62cb118 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 15:27:25 +0100 Subject: [PATCH 05/58] [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 e69de29bb2d1..2992579de638 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 b157a67994ea..f9ebeb25d158 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 fe3f82d31c4c..625a2d20fed4 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 24adac026c49075696e784578f607ed55db5130b Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 12 Mar 2015 15:44:57 +0100 Subject: [PATCH 06/58] [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 f9ebeb25d158..df8b4930803b 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 625a2d20fed4..2950439c1e51 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 3f5155a32e8d953d9ec70c9d0f0ae7fd575f19fb Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 15:29:09 +0100 Subject: [PATCH 07/58] [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 2992579de638..d33d4f21bdfb 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 0ff664d9120b7a6c2d8cfc442df7aeab3be36fe8 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 15:34:02 +0100 Subject: [PATCH 08/58] [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 df8b4930803b..058a682e268b 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 2950439c1e51..4f587e3f08de 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 483b02ff0798455cc67197a394a69a6940d385a9 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 17:36:39 +0100 Subject: [PATCH 09/58] [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 058a682e268b..9a7b8ca62ddf 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 61f2cb4967b2b4e6d385f11537fbd3201c7c95ed Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 17:44:41 +0100 Subject: [PATCH 10/58] [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 9a7b8ca62ddf..0425468bfe2a 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 3f35550f818e262b0480f1f4cd61047b1393f5ab Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 18:19:42 +0100 Subject: [PATCH 11/58] [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/58] [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 0425468bfe2a..191ffad66b90 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 555de4a0f55f55ab4d188247e6b7724d6e7caacb Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 13 Mar 2015 18:47:15 +0100 Subject: [PATCH 13/58] [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 6be504c44bf3..5fb296beb0ed 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 191ffad66b90..0c818548d926 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 9b460f5f12f0fc1a2f4e66fa4adf3b5a61163c63 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 16:35:17 +0100 Subject: [PATCH 14/58] [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 4f587e3f08de..952a003f647d 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 49131e0a37d1b301bc7c516b417dcaabf2434157 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 16:47:21 +0100 Subject: [PATCH 15/58] [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 0c818548d926..12a56c8cb56a 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 952a003f647d..35f1669bc197 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 b87b5e521399222fc7c65ffe929982f51b6eb76c Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 16 Mar 2015 17:20:43 +0100 Subject: [PATCH 16/58] [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 5fb296beb0ed..0b145aaf6cab 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/58] [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 410bf221acb5ac20911fabf953655ebc2c79f58c Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 18 Mar 2015 17:10:29 +0100 Subject: [PATCH 18/58] [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 12a56c8cb56a..d4828b47c19e 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 e8d60148963c24ff88f544b66c180f5c2c26fc7f Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 16 Apr 2015 09:59:47 +0200 Subject: [PATCH 19/58] [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 d4828b47c19e..e1021457aba9 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 5f0d89087d387137e1c131e300da715587d967e2 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 16 Apr 2015 10:15:39 +0200 Subject: [PATCH 20/58] [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 e1021457aba9..5d4ce7854587 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 fe6c41fe3106ba179fdd97942c7c5f86e5b9adbb Mon Sep 17 00:00:00 2001 From: Yannick Vaucher Date: Fri, 22 May 2015 19:45:36 +0200 Subject: [PATCH 21/58] 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 0b145aaf6cab..a6b436e17cf9 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 b051d46890f02a38feaa465b2117d8745c8d2c52 Mon Sep 17 00:00:00 2001 From: Markus Schneider Date: Thu, 4 Jun 2015 14:30:25 +0200 Subject: [PATCH 22/58] 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 1cbc4aad73cb..95a3299b2ec2 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 8705e577d1f50dc94b426f100fd4478f86829ef2 Mon Sep 17 00:00:00 2001 From: Markus Schneider Date: Fri, 5 Jun 2015 00:33:22 +0200 Subject: [PATCH 23/58] 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 95a3299b2ec2..2e43203a8bd5 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 a31ee8b5406a9f2767a51a1c81ade6894df9b2c2 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/58] [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 2e43203a8bd5..0b652e3cd18f 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 49ef856baf1933adcfabe9673fdeeb81ea029761 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 14 Oct 2015 02:57:05 +0200 Subject: [PATCH 25/58] [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 0b652e3cd18f..e48c3a6e6d6a 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 004b29ee9b9265c0439bcc3fa7485e87075484bb Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Mon, 23 Nov 2015 23:43:23 -0500 Subject: [PATCH 26/58] 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 000000000000..7a85d2bde813 --- /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 000000000000..337d2b944ef1 --- /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 000000000000..10ba2f9f8b08 --- /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 000000000000..df37d34a7088 --- /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 000000000000..7ed8bc355a41 --- /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 000000000000..f209e294170b --- /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 000000000000..5b5d0bf31c36 --- /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 000000000000..c56e07fa61ce --- /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 000000000000..07ae09c5d338 --- /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 000000000000..635773bda8e4 --- /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 165e962c779bff726a75211b71c8a583719cf6fa Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 18 Jan 2016 16:41:25 +0100 Subject: [PATCH 27/58] [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 a6b436e17cf9..7c880b1316aa 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 5d4ce7854587..4dbcb4cc9c71 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 c0c6289b43668edffc60f9cece30febefab8befd Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 14 Sep 2016 09:32:56 +0200 Subject: [PATCH 28/58] [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 7c880b1316aa..83c29328c1d1 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 e48c3a6e6d6a..87dc3541c5b9 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 4dbcb4cc9c71..5f6147f4d09e 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 35f1669bc197..ca6b687f53b2 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 9e9b934bf8a6106501f89e6ef5ed3005a5d26a15 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Fri, 16 Sep 2016 14:35:54 +0200 Subject: [PATCH 29/58] [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 83c29328c1d1..dc8a480f1eae 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 87dc3541c5b9..a8f4e8cf07f7 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 5f6147f4d09e..4087e88e379b 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 c1a171316b99acb87552fc2b8d6d5316707d8ec3 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 16 Sep 2016 17:56:53 +0200 Subject: [PATCH 30/58] [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 4087e88e379b..e570949d85da 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 5990b0247dad0ac772e5e7bb2428095b85979c00 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 21 Sep 2016 18:24:51 +0200 Subject: [PATCH 31/58] [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 dc8a480f1eae..6628a81ea877 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 e570949d85da..c5631593097b 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 65424427e52b65fb0786a2dc01403ccec025ea62 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 21 Sep 2016 20:39:15 +0200 Subject: [PATCH 32/58] [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 c5631593097b..782ed21a8235 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 606c25a712fd043180c40db4acbd15fc0c8d5018 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 09:45:23 +0200 Subject: [PATCH 33/58] [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 782ed21a8235..3e182047b807 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 4f4001911154567cbff6b11ad00e94cd57e4da46 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 09:48:03 +0200 Subject: [PATCH 34/58] [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 6628a81ea877..85d5dd712515 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 50a3af9c7d04810bea0eb776e4cde61c943ff59b Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 12:09:34 +0200 Subject: [PATCH 35/58] [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 3e182047b807..8d2e3fc680fd 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 b4f1a37c1b4340fb4387dd0d4d3e257d2b2c2e47 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 22 Sep 2016 12:11:40 +0200 Subject: [PATCH 36/58] [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 8d2e3fc680fd..8a51b3a6b8b0 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 20ed7d3803cfe4a910d2e427109c9193d341f8d4 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 6 Oct 2016 16:12:54 +0200 Subject: [PATCH 37/58] [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 a8f4e8cf07f7..8b55c97d5e97 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 b35d088eb0beeeeab9637a4d7666d5edff1c5690 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 6 Oct 2016 16:13:01 +0200 Subject: [PATCH 38/58] [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 cbb2e8a0c934099b308f03542c9f358e45ea094c Mon Sep 17 00:00:00 2001 From: jesusVMayor Date: Mon, 24 Apr 2017 12:28:47 +0200 Subject: [PATCH 39/58] 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 8b55c97d5e97..b4651c0741ff 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 d33d4f21bdfb..14ed1c5364fd 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 8a51b3a6b8b0..43fa84bb4998 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 ca6b687f53b2..a1a0d52151ca 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 f7881e122cd271ca910ae76fb81ad52f7359c3d9 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Fri, 28 Apr 2017 20:01:00 +0200 Subject: [PATCH 40/58] [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 85d5dd712515..40098bff14fe 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 0f7f30ab202178d91a9350bf9d1ae413a9dce20c 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/58] [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 40098bff14fe..d81d100cca64 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 2fc7a002b2234845321f5e0a307336c17b03a678 Mon Sep 17 00:00:00 2001 From: Richard deMeester Date: Thu, 31 Aug 2017 01:03:06 +1000 Subject: [PATCH 42/58] [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 b4651c0741ff..69b703cd5678 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 43fa84bb4998..ec88f1eafd1a 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 a1a0d52151ca..b7aaaefe1891 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 908dc89802458053ef543246a99dec33530abaeb Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Wed, 17 Jan 2018 10:50:20 +0100 Subject: [PATCH 43/58] 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 000000000000..d9620d989bed --- /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 000000000000..27efab7f526e --- /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 27aed59f5b2b7c2ca159f48602298f4739eac9f0 Mon Sep 17 00:00:00 2001 From: Artem Kostyuk Date: Thu, 15 Feb 2018 09:45:16 +0200 Subject: [PATCH 44/58] [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 d81d100cca64..6fb555b94582 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 faef9dac007f..919541c6cab7 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 69b703cd5678..41f69a75ebb0 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 d9620d989bed..57a65fc55590 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 27efab7f526e..e1fde063c22b 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 ec88f1eafd1a..2c0a0cd92493 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 935bd4280bbb606db6a029d52f3dd7652df9fd5b Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 19 Feb 2018 17:48:06 +0100 Subject: [PATCH 45/58] [MIG+REF][11] web_widget_x2many_2d_matrix The widget has been completely refactored to benefit from the new MVC paradigm introduced in v11. --- 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 +- 10 files changed, 614 insertions(+), 515 deletions(-) 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/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst index 6fb555b94582..52eb81b1336f 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 41f69a75ebb0..31fa2d5a97e4 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 000000000000..898ac0d58133 --- /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 2c0a0cd92493..000000000000 --- 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 000000000000..4b1a73f9cd74 --- /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 b7aaaefe1891..000000000000 --- 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 06934cc33dbc..ba820435cb41 100644 --- a/web_widget_x2many_2d_matrix/views/templates.xml +++ b/web_widget_x2many_2d_matrix/views/assets.xml @@ -3,7 +3,8 @@