mirror of https://github.com/OCA/web.git
[FIX] web_widget_x2many_2d_matrix: Enable keyboard navigation
parent
36913cd896
commit
f5dde1577d
|
@ -39,7 +39,9 @@ Use this widget by saying::
|
||||||
|
|
||||||
This assumes that my_field refers to a model with the fields `x`, `y` and
|
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
|
`value`. If your fields are named differently, pass the correct names as
|
||||||
attributes::
|
attributes:
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
<field name="my_field" widget="x2many_2d_matrix" field_x_axis="my_field1" field_y_axis="my_field2" field_value="my_field3">
|
<field name="my_field" widget="x2many_2d_matrix" field_x_axis="my_field1" field_y_axis="my_field2" field_value="my_field3">
|
||||||
<tree>
|
<tree>
|
||||||
|
@ -80,7 +82,9 @@ 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
|
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
|
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
|
data model and point to it from our wizard. The crucial part is that we fill
|
||||||
the field in the default function::
|
the field in the default function:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
from odoo import fields, models
|
from odoo import fields, models
|
||||||
|
|
||||||
|
@ -102,7 +106,8 @@ the field in the default function::
|
||||||
'message_needaction': False,
|
'message_needaction': False,
|
||||||
'date_deadline': fields.Date.today(),
|
'date_deadline': fields.Date.today(),
|
||||||
})
|
})
|
||||||
# if the project doesn't have a task for the user, create a new one
|
# 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
|
if not p.task_ids.filtered(lambda x: x.user_id == u) else
|
||||||
# otherwise, return the task
|
# otherwise, return the task
|
||||||
(4, p.task_ids.filtered(lambda x: x.user_id == u)[0].id)
|
(4, p.task_ids.filtered(lambda x: x.user_id == u)[0].id)
|
||||||
|
@ -112,7 +117,9 @@ the field in the default function::
|
||||||
|
|
||||||
task_ids = fields.Many2many('project.task', default=_default_task_ids)
|
task_ids = fields.Many2many('project.task', default=_default_task_ids)
|
||||||
|
|
||||||
Now in our wizard, we can use::
|
Now in our wizard, we can use:
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
<field name="task_ids" widget="x2many_2d_matrix" field_x_axis="project_id" field_y_axis="user_id" field_value="planned_hours">
|
<field name="task_ids" widget="x2many_2d_matrix" field_x_axis="project_id" field_y_axis="user_id" field_value="planned_hours">
|
||||||
<tree>
|
<tree>
|
||||||
|
@ -123,7 +130,6 @@ Now in our wizard, we can use::
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
|
|
||||||
|
|
||||||
Known issues / Roadmap
|
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 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
|
Bug Tracker
|
||||||
===========
|
===========
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (require) {
|
odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// heavily inspired by Odoo's `ListRenderer`
|
// Heavily inspired by Odoo's `ListRenderer`
|
||||||
var BasicRenderer = require('web.BasicRenderer');
|
var BasicRenderer = require('web.BasicRenderer');
|
||||||
var config = require('web.config');
|
var config = require('web.config');
|
||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
var field_utils = require('web.field_utils');
|
var field_utils = require('web.field_utils');
|
||||||
var _t = core._t;
|
var _t = core._t;
|
||||||
var FIELD_CLASSES = {
|
var FIELD_CLASSES = {
|
||||||
// copied from ListRenderer
|
// Copied from ListRenderer
|
||||||
float: 'o_list_number',
|
float: 'o_list_number',
|
||||||
integer: 'o_list_number',
|
integer: 'o_list_number',
|
||||||
monetary: '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) {
|
init: function (parent, state, params) {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
this.editable = params.editable;
|
this.editable = params.editable;
|
||||||
this.columns = params.matrix_data.columns;
|
this._saveMatrixData(params.matrix_data);
|
||||||
this.rows = params.matrix_data.rows;
|
},
|
||||||
this.matrix_data = 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
|
* @private
|
||||||
* @returns {String} a string with the generated html.
|
* @returns {String} a string with the generated html.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
_renderRows: function () {
|
_renderRows: function () {
|
||||||
return _.map(this.rows, this._renderRow.bind(this));
|
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]));
|
$tr = $tr.append(self._renderLabelCell(row.data[0]));
|
||||||
var $cells = _.map(this.columns, function (node, index) {
|
var $cells = _.map(this.columns, function (node, index) {
|
||||||
var record = row.data[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;
|
node.attrs.name = self.matrix_data.field_value;
|
||||||
return self._renderBodyCell(record, node, index, {mode:''});
|
return self._renderBodyCell(record, node, index, {mode:''});
|
||||||
});
|
});
|
||||||
|
@ -203,10 +211,10 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
|
||||||
var $td = $('<td>');
|
var $td = $('<td>');
|
||||||
var value = record.data[this.matrix_data.field_y_axis];
|
var value = record.data[this.matrix_data.field_y_axis];
|
||||||
if (value.type === 'record') {
|
if (value.type === 'record') {
|
||||||
// we have a related record
|
// We have a related record
|
||||||
value = value.data.display_name;
|
value = value.data.display_name;
|
||||||
}
|
}
|
||||||
// get 1st column filled w/ Y label
|
// Get 1st column filled w/ Y label
|
||||||
$td.text(value);
|
$td.text(value);
|
||||||
return $td;
|
return $td;
|
||||||
},
|
},
|
||||||
|
@ -272,7 +280,7 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
|
||||||
if (modifiers.invisible && !(options && options.renderInvisible)) {
|
if (modifiers.invisible && !(options && options.renderInvisible)) {
|
||||||
return $td;
|
return $td;
|
||||||
}
|
}
|
||||||
// enforce mode of the parent
|
// Enforce mode of the parent
|
||||||
options.mode = this.getParent().mode;
|
options.mode = this.getParent().mode;
|
||||||
var widget = this._renderFieldWidget(
|
var widget = this._renderFieldWidget(
|
||||||
node, record, _.pick(options, 'mode')
|
node, record, _.pick(options, 'mode')
|
||||||
|
@ -329,7 +337,7 @@ odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (requ
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var type = field.type;
|
var type = field.type;
|
||||||
if (!_.inArray(type, ['integer', 'float', 'monetary'])) {
|
if (!~['integer', 'float', 'monetary'].indexOf(type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_.each(this.columns, function (column, index) {
|
_.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) {
|
_.each(this.rows, function (row) {
|
||||||
column.aggregate.value += row.data[index].data[fname];
|
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;
|
return;
|
||||||
}
|
}
|
||||||
var type = field.type;
|
var type = field.type;
|
||||||
if (!_.inArray(type, ['integer', 'float', 'monetary'])) {
|
if (!~['integer', 'float', 'monetary'].indexOf(type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_.each(this.rows, function (row) {
|
_.each(this.rows, function (row) {
|
||||||
|
|
|
@ -56,7 +56,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
|
||||||
node[property];
|
node[property];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// and this?
|
// And this?
|
||||||
this.field_editability =
|
this.field_editability =
|
||||||
node.field_editability || this.field_editability;
|
node.field_editability || this.field_editability;
|
||||||
this.show_row_totals =
|
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],
|
var x = record.data[this.field_x_axis],
|
||||||
y = record.data[this.field_y_axis];
|
y = record.data[this.field_y_axis];
|
||||||
if (x.type === 'record') {
|
if (x.type === 'record') {
|
||||||
// we have a related record
|
// We have a related record
|
||||||
x = x.data.display_name;
|
x = x.data.display_name;
|
||||||
}
|
}
|
||||||
if (y.type === 'record') {
|
if (y.type === 'record') {
|
||||||
// we have a related record
|
// We have a related record
|
||||||
y = y.data.display_name;
|
y = y.data.display_name;
|
||||||
}
|
}
|
||||||
this.by_x_axis[x] = this.by_x_axis[x] || {};
|
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_x_axis[x][y] = record;
|
||||||
this.by_y_axis[y][x] = record;
|
this.by_y_axis[y][x] = record;
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
// init columns
|
// Init columns
|
||||||
this.columns = [];
|
this.columns = [];
|
||||||
$.each(this.by_x_axis, function (x) {
|
$.each(this.by_x_axis, function (x) {
|
||||||
this.columns.push(this._make_column(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) {
|
_make_column: function (x) {
|
||||||
return {
|
return {
|
||||||
// simulate node parsed on xml arch
|
// Simulate node parsed on xml arch
|
||||||
'tag': 'field',
|
'tag': 'field',
|
||||||
'attrs': {
|
'attrs': {
|
||||||
'name': this.field_x_axis,
|
'name': this.field_x_axis,
|
||||||
|
@ -137,7 +137,7 @@ odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
|
||||||
*/
|
*/
|
||||||
_make_row: function (y) {
|
_make_row: function (y) {
|
||||||
var self = this;
|
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': []};
|
var row = {'data': []};
|
||||||
$.each(self.by_x_axis, function (x) {
|
$.each(self.by_x_axis, function (x) {
|
||||||
row.data.push(self.by_y_axis[y][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
|
// Ensure widget is re initiated when rendering
|
||||||
this.init_matrix();
|
this.init_matrix();
|
||||||
var arch = this.view.arch,
|
var arch = this.view.arch;
|
||||||
viewType = 'list';
|
// 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, {
|
this.renderer = new X2Many2dMatrixRenderer(this, this.value, {
|
||||||
arch: arch,
|
arch: arch,
|
||||||
editable: true,
|
editable: this.mode === 'edit' && arch.attrs.editable,
|
||||||
viewType: viewType,
|
viewType: "list",
|
||||||
matrix_data: this.matrix_data,
|
matrix_data: this.matrix_data,
|
||||||
});
|
});
|
||||||
this.$el.addClass('o_field_x2many o_field_x2many_2d_matrix');
|
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);
|
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);
|
field_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix);
|
||||||
|
|
Loading…
Reference in New Issue