mirror of https://github.com/OCA/web.git
[FIX] web_widget_x2many_2d_matrix: Enable keyboard navigation
parent
510956042e
commit
c273fdf6d2
|
@ -39,7 +39,9 @@ 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
|
||||
|
||||
<field name="my_field" widget="x2many_2d_matrix" field_x_axis="my_field1" field_y_axis="my_field2" field_value="my_field3">
|
||||
<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
|
||||
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::
|
||||
the field in the default function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
@ -102,7 +106,8 @@ the field in the default function::
|
|||
'message_needaction': False,
|
||||
'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
|
||||
# otherwise, return the task
|
||||
(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)
|
||||
|
||||
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">
|
||||
<tree>
|
||||
|
@ -123,7 +130,6 @@ Now in our wizard, we can use::
|
|||
</tree>
|
||||
</field>
|
||||
|
||||
|
||||
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
|
||||
===========
|
||||
|
|
|
@ -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 = $('<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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue