').append(' | ').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;}
+ * @override
+ */
+ init: function (parent, state, params) {
+ this._super.apply(this, arguments);
+ this.editable = params.editable;
+ 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;
+ },
- return X2Many2dMatrixRenderer;
+ /**
+ * 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;
+
+ // Display a nice message if there's no data to display
+ this.$el.empty();
+ if (!self.rows.length) {
+ var $alert = $('', {'class': 'alert alert-info'});
+ $alert.text(_t('Sorry no matrix data to display.'));
+ this.$el.append($alert);
+ return this._super();
+ }
+
+ 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
+ * @returns {jQueryElement} The table body element 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
+ * @returns {jQueryElement} The thead element that was inserted into.
+ */
+ _renderHeader: function () {
+ var $tr = $('').append(' | ');
+ $tr = $tr.append(_.map(
+ this.columns,
+ this._renderHeaderCell.bind(this)
+ ));
+ if (this.matrix_data.show_row_totals) {
+ $tr.append($(' | ', {class: 'total'}));
+ }
+ return $('').append($tr);
+ },
+
+ /**
+ * Render a single header cell.
+ *
+ * Creates a th and adds the description as text.
+ *
+ * @private
+ * @param {jQueryElement} node
+ * @returns {jQueryElement} the created node.
+ */
+ _renderHeaderCell: function (node) {
+ var name = node.attrs.name;
+ var field = this.state.fields[name];
+ var $th = $(' | ');
+ if (!field) {
+ return $th;
+ }
+ var description = null;
+ if (node.attrs.widget) {
+ description = this.state.fieldsInfo.list[name]
+ .Widget.prototype.description;
+ }
+ if (_.isNull(description)) {
+ 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 | .
+ * 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 $tr = $(' ', {class: 'o_data_row'}),
+ _data = _.without(row.data, undefined);
+ $tr = $tr.append(this._renderLabelCell(_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 = this.matrix_data.field_value;
+ return this._renderBodyCell(record, node, index, {mode:''});
+ }.bind(this));
+ $tr = $tr.append($cells);
+ if (row.aggregate) {
+ $tr.append(this._renderAggregateRowCell(row));
+ }
+ return $tr;
+ },
+
+ /**
+ * Renders the label for a specific row.
+ *
+ * @private
+ * @param {Object} record Contains the information about the record.
+ * @returns {jQueryElement} the cell that was rendered.
+ */
+ _renderLabelCell: function (record) {
+ var $td = $('');
+ 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,
+ });
+ if (_.isUndefined(record)) {
+ // Without record, nothing elese to do
+ return $td;
+ }
+ $td.attr({
+ '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;
+ }
+ // Enforce mode of the parent
+ options.mode = this.getParent().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(
+ $('').append(' | ').append($cells)
+ );
+ }
+ },
+
+ /**
+ * Render the Aggregate cells for the column.
+ *
+ * @private
+ * @returns {List} the rendered cells
+ */
+ _renderAggregateColCells: function () {
+ var self = this;
+ return _.map(this.columns, function (column) {
+ 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 fname = this.matrix_data.field_value,
+ field = this.state.fields[fname];
+ if (!field) {
+ return;
+ }
+ var type = field.type;
+ if (!~['integer', 'float', 'monetary'].indexOf(type)) {
+ return;
+ }
+ _.each(this.columns, function (column, index) {
+ column.aggregate = {
+ fname: fname,
+ ftype: type,
+ help: _t('Sum'),
+ value: 0,
+ };
+ _.each(this.rows, function (row) {
+ // TODO Use only one _.propertyOf in underscore 1.9.0+
+ try {
+ column.aggregate.value += row.data[index].data[fname];
+ } catch (error) {
+ // Nothing to do
+ }
+ });
+ }.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();
+ }
+ },
+
+ /**
+ * 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 fname = this.matrix_data.field_value,
+ field = this.state.fields[fname];
+ if (!field) {
+ return;
+ }
+ var type = field.type;
+ if (!~['integer', 'float', 'monetary'].indexOf(type)) {
+ return;
+ }
+ _.each(this.rows, function (row) {
+ row.aggregate = {
+ fname: fname,
+ ftype: type,
+ help: _t('Sum'),
+ value: 0,
+ };
+ _.each(row.data, function (col) {
+ // TODO Use _.property in underscore 1.9+
+ try {
+ row.aggregate.value += col.data[fname];
+ } catch (error) {
+ // Nothing to do
+ }
+ });
+ });
+ },
+
+ /**
+ * 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.
+ *
+ * @param {Object} state
+ * Contains the current state of the field & all the data
+ *
+ * @param {String} id
+ * the id of the updated object.
+ *
+ * @param {Array} fields
+ * The fields we have in the view.
+ *
+ * @param {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
+ * @param {String} id Datapoint ID
+ */
+ _refresh: function (id) {
+ this._updateRow(id);
+ this._refreshColTotals();
+ this._refreshRowTotals();
+ },
+
+ /**
+ *Update row data in our internal rows.
+ *
+ * @param {String} id: The id of the row that needs to be updated.
+ */
+ _updateRow: function (id) {
+ var record = _.findWhere(this.state.data, {id: id}),
+ _id = _.property("id");
+ _.each(this.rows, function (row) {
+ _.each(row.data, function (col, i) {
+ if (_id(col) === 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));
+ }
+ });
+ },
+
+ /**
+ * X2many fields expect this
+ *
+ * @returns {null}
+ */
+ getEditableRecordID: function () {
+ return null;
+ },
+
+ });
+
+ return X2Many2dMatrixRenderer;
});
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 56d2598fe..addf7cd40 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
@@ -4,177 +4,221 @@
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
- "use strict";
+ "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 field_registry = require('web.field_registry');
+ var relational_fields = require('web.relational_fields');
+ 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();
- },
+ var WidgetX2Many2dMatrix = relational_fields.FieldOne2Many.extend({
+ widget_class: 'o_form_field_x2many_2d_matrix',
- /**
- * 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');
+ /**
+ * Initialize the widget & parameters.
+ *
+ * @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.
+ */
+ init: function (parent, name, record, options) {
+ this._super(parent, name, record, options);
+ this.init_params();
+ },
- },
- /**
- * 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;
- // Wipe the content if something still exists
- this.by_x_axis = {};
- this.by_y_axis = {};
- _.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
- };
+ /**
+ * 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');
+ },
- },
- /**
- * 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();
- }
- // Ensure widget is re initiated when rendering
- this.init_matrix();
- 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');
- // 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);
+ /**
+ * Initializes the Value matrix.
+ *
+ * Puts the values in the grid.
+ * If we have related items we use the display name.
+ */
+ init_matrix: function () {
+ var records = this.recordData[this.name].data;
+ // Wipe the content if something still exists
+ this.by_x_axis = {};
+ this.by_y_axis = {};
+ _.each(records, function (record) {
+ var x = record.data[this.field_x_axis],
+ y = record.data[this.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;
+ }
+ 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] = record;
+ this.by_y_axis[y][x] = record;
+ }.bind(this));
+ // Init columns
+ this.columns = [];
+ $.each(this.by_x_axis, function (x) {
+ this.columns.push(this._make_column(x));
+ }.bind(this));
+ this.rows = [];
+ $.each(this.by_y_axis, function (y) {
+ this.rows.push(this._make_row(y));
+ }.bind(this));
+ this.matrix_data = {
+ 'field_value': this.field_value,
+ 'field_x_axis': this.field_x_axis,
+ 'field_y_axis': this.field_y_axis,
+ 'columns': this.columns,
+ 'rows': this.rows,
+ 'show_row_totals': this.show_row_totals,
+ 'show_column_totals': this.show_column_totals,
+ };
+ },
- }
+ /**
+ * Create scaffold for a column.
+ *
+ * @param {String} x The string used as a column title
+ * @returns {Object}
+ */
+ _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.
+ *
+ * @param {String} y The string used as a row title
+ * @returns {Object}
+ */
+ _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;
+ },
- field_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix);
+ /**
+ * Parse a String containing a bool and convert it to a JS bool.
+ *
+ * @param {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;
+ },
- return {
- WidgetX2Many2dMatrix: WidgetX2Many2dMatrix
- };
+ /**
+ * 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();
+ }
+ // Ensure widget is re initiated when rendering
+ this.init_matrix();
+ 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: this.mode === 'edit' && arch.attrs.editable,
+ viewType: "list",
+ matrix_data: this.matrix_data,
+ });
+ this.$el.addClass('o_field_x2many o_field_x2many_2d_matrix');
+ 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
+ // TODO Use _.propertyOf in underscore 1.9+
+ try {
+ this._backwards = options.event.data.direction === "previous";
+ } catch (error) {
+ this._backwards = false;
+ }
+ 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);
+
+ return {
+ WidgetX2Many2dMatrix: WidgetX2Many2dMatrix,
+ };
});
| |