diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst
index aba2f90a..44f5920c 100644
--- a/web_widget_x2many_2d_matrix/README.rst
+++ b/web_widget_x2many_2d_matrix/README.rst
@@ -39,16 +39,18 @@ 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::
+attributes:
-
-
-
-
-
-
-
-
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
You can pass the following parameters:
@@ -80,49 +82,53 @@ 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::
-
- from odoo import fields, models
-
- 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, {
- 'name': 'Sample task name',
- '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
- (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::
-
-
-
-
-
-
-
-
-
-
+the field in the default function:
+
+.. code-block:: python
+
+ from odoo import fields, models
+
+ 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, {
+ 'name': 'Sample task name',
+ '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
+ (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:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
Known issues / Roadmap
======================
@@ -134,6 +140,11 @@ Known issues / Roadmap
* Support limit total records in the matrix. Ref: https://github.com/OCA/web/issues/901
+* Support cell traversal through keyboard arrows.
+
+* Entering the widget from behind by pressing ``Shift+TAB`` in your keyboard
+ will enter into the 1st cell until https://github.com/odoo/odoo/pull/26490
+ is merged.
Bug Tracker
===========
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
index 6cb66ac2..dcbc44ad 100644
--- 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
@@ -4,14 +4,14 @@
odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (require) {
"use strict";
- // heavily inspired by Odoo's `ListRenderer`
+ // Heavily inspired by Odoo's `ListRenderer`
var BasicRenderer = require('web.BasicRenderer');
var config = require('web.config');
var core = require('web.core');
var field_utils = require('web.field_utils');
var _t = core._t;
var FIELD_CLASSES = {
- // copied from ListRenderer
+ // Copied from ListRenderer
float: 'o_list_number',
integer: 'o_list_number',
monetary: 'o_list_number',
@@ -26,9 +26,18 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
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;
+ this._saveMatrixData(params.matrix_data);
+ },
+
+ /**
+ * Update matrix data in current renderer instance.
+ *
+ * @param {Object} matrixData Contains the matrix data
+ */
+ _saveMatrixData: function (matrixData) {
+ this.columns = matrixData.columns;
+ this.rows = matrixData.rows;
+ this.matrix_data = matrixData;
},
/**
@@ -159,7 +168,6 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
*
* @private
* @returns {String} a string with the generated html.
- *
*/
_renderRows: function () {
return _.map(this.rows, this._renderRow.bind(this));
@@ -181,7 +189,7 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
$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
+ // 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:''});
});
@@ -203,10 +211,10 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
var $td = $('
');
var value = record.data[this.matrix_data.field_y_axis];
if (value.type === 'record') {
- // we have a related record
+ // We have a related record
value = value.data.display_name;
}
- // get 1st column filled w/ Y label
+ // Get 1st column filled w/ Y label
$td.text(value);
return $td;
},
@@ -272,7 +280,7 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
if (modifiers.invisible && !(options && options.renderInvisible)) {
return $td;
}
- // enforce mode of the parent
+ // Enforce mode of the parent
options.mode = this.getParent().mode;
var widget = this._renderFieldWidget(
node, record, _.pick(options, 'mode')
@@ -329,7 +337,7 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
return;
}
var type = field.type;
- if (!_.inArray(type, ['integer', 'float', 'monetary'])) {
+ if (!~['integer', 'float', 'monetary'].indexOf(type)) {
return;
}
_.each(this.columns, function (column, index) {
@@ -342,7 +350,45 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
_.each(this.rows, function (row) {
column.aggregate.value += row.data[index].data[fname];
});
- });
+ }.bind(this));
+ },
+
+ /**
+ * @override
+ */
+ updateState: function (state, params) {
+ if (params.matrix_data) {
+ this._saveMatrixData(params.matrix_data);
+ }
+ return this._super.apply(this, arguments);
+ },
+
+ /**
+ * Traverse the fields matrix with the keyboard
+ *
+ * @override
+ * @private
+ * @param {OdooEvent} event "navigation_move" event
+ */
+ _onNavigationMove: function (event) {
+ var widgets = this.__parentedChildren,
+ index = widgets.indexOf(event.target),
+ first = index === 0,
+ last = index === widgets.length - 1,
+ move = 0;
+ // Guess if we have to move the focus
+ if (event.data.direction === "next" && !last) {
+ move = 1;
+ } else if (event.data.direction === "previous" && !first) {
+ move = -1;
+ }
+ // Move focus
+ if (move) {
+ var target = widgets[index + move];
+ index = this.allFieldWidgets[target.record.id].indexOf(target);
+ this._activateFieldWidget(target.record, index, {inc: 0});
+ event.stopPropagation();
+ }
},
/**
@@ -362,7 +408,7 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
return;
}
var type = field.type;
- if (!_.inArray(type, ['integer', 'float', 'monetary'])) {
+ if (!~['integer', 'float', 'monetary'].indexOf(type)) {
return;
}
_.each(this.rows, function (row) {
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
index 420c624c..638c080a 100644
--- 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
@@ -16,9 +16,9 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
widget_class: 'o_form_field_x2many_2d_matrix',
/**
- * Initialize the widget & parameters.
+ *Initialize the widget & parameters.
*
- * @param {Object} parent contains the form view.
+ *@param {Object} parent contains the form view.
* @param {String} name the name of the field.
* @param {Object} record information about the database records.
* @param {Object} options view options.
@@ -29,7 +29,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
},
/**
- * Initialize the widget specific parameters.
+ *Initialize the widget specific parameters.
* Sets the axis and the values.
*/
init_params: function () {
@@ -56,7 +56,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
node[property];
}
}
- // and this?
+ // And this?
this.field_editability =
node.field_editability || this.field_editability;
this.show_row_totals =
@@ -80,11 +80,11 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
var x = record.data[this.field_x_axis],
y = record.data[this.field_y_axis];
if (x.type === 'record') {
- // we have a related record
+ // We have a related record
x = x.data.display_name;
}
if (y.type === 'record') {
- // we have a related record
+ // We have a related record
y = y.data.display_name;
}
this.by_x_axis[x] = this.by_x_axis[x] || {};
@@ -92,7 +92,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
this.by_x_axis[x][y] = record;
this.by_y_axis[y][x] = record;
}.bind(this));
- // init columns
+ // Init columns
this.columns = [];
$.each(this.by_x_axis, function (x) {
this.columns.push(this._make_column(x));
@@ -120,7 +120,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
*/
_make_column: function (x) {
return {
- // simulate node parsed on xml arch
+ // Simulate node parsed on xml arch
'tag': 'field',
'attrs': {
'name': this.field_x_axis,
@@ -137,7 +137,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
*/
_make_row: function (y) {
var self = this;
- // use object so that we can attach more data if needed
+ // 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]);
@@ -170,21 +170,45 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
}
// Ensure widget is re initiated when rendering
this.init_matrix();
- var arch = this.view.arch,
- viewType = 'list';
+ var arch = this.view.arch;
+ // Update existing renderer
+ if (!_.isUndefined(this.renderer)) {
+ return this.renderer.updateState(this.value, {
+ matrix_data: this.matrix_data,
+ });
+ }
+ // Create a new matrix renderer
this.renderer = new X2Many2dMatrixRenderer(this, this.value, {
arch: arch,
- editable: true,
- viewType: viewType,
+ editable: this.mode === 'edit' && arch.attrs.editable,
+ viewType: "list",
matrix_data: this.matrix_data,
});
this.$el.addClass('o_field_x2many o_field_x2many_2d_matrix');
- // Remove previous rendered and add the newly created one
- this.$el.find('div:not(.o_x2m_control_panel)').remove();
return this.renderer.appendTo(this.$el);
+ },
+ /**
+ * Activate the widget.
+ *
+ * @override
+ */
+ activate: function (options) {
+ // Won't work fine without https://github.com/odoo/odoo/pull/26490
+ this._backwards = options.event.data.direction === "previous";
+ var result = this._super.apply(this, arguments);
+ delete this._backwards;
+ return result;
},
+ /**
+ * Get first element to focus.
+ *
+ * @override
+ */
+ getFocusableElement: function () {
+ return this.$(".o_input:" + (this._backwards ? "last" : "first"));
+ },
});
field_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix);
|