forked from Techsystech/web
[FIX] web_widget_x2many_2d_matrix: Fix linters
parent
cce5cebab2
commit
c83ef4a9fa
|
@ -154,7 +154,7 @@ Contributors
|
||||||
* Artem Kostyuk <a.kostyuk@mobilunity.com>
|
* Artem Kostyuk <a.kostyuk@mobilunity.com>
|
||||||
* Simone Orsi <simone.orsi@camptocamp.com>
|
* Simone Orsi <simone.orsi@camptocamp.com>
|
||||||
* Timon Tschanz <timon.tschanz@camptocamp.com>
|
* Timon Tschanz <timon.tschanz@camptocamp.com>
|
||||||
|
* Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
|
|
||||||
Maintainer
|
Maintainer
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -2,422 +2,491 @@
|
||||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
||||||
|
|
||||||
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 field_utils = require('web.field_utils');
|
var core = require('web.core');
|
||||||
var utils = require('web.utils');
|
var field_utils = require('web.field_utils');
|
||||||
var core = require('web.core');
|
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',
|
text: 'o_list_text',
|
||||||
text: 'o_list_text',
|
};
|
||||||
};
|
|
||||||
|
|
||||||
var X2Many2dMatrixRenderer = BasicRenderer.extend({
|
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;
|
|
||||||
|
|
||||||
// Display a nice message if there's no data to display
|
|
||||||
this.$el.empty();
|
|
||||||
if (!self.rows.length){
|
|
||||||
var $alert = $('<div>', {'class': 'alert alert-info'});
|
|
||||||
$alert.text(_t('Sorry no matrix data to display.'));
|
|
||||||
this.$el.append($alert);
|
|
||||||
return this._super();
|
|
||||||
}
|
|
||||||
|
|
||||||
var $table = $('<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 = $('<tbody>').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 = $('<tr>').append('<th/>');
|
|
||||||
$tr= $tr.append(_.map(this.columns, this._renderHeaderCell.bind(this)));
|
|
||||||
if (this.matrix_data.show_row_totals) {
|
|
||||||
$tr.append($('<th/>', {class: 'total'}));
|
|
||||||
}
|
|
||||||
return $('<thead>').append($tr);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Render a single header cell. Creates a th and adds the description as text.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {jQueryElement} node
|
|
||||||
* @returns {jQueryElement} the created <th> node.
|
|
||||||
*/
|
|
||||||
_renderHeaderCell: function (node) {
|
|
||||||
var name = node.attrs.name;
|
|
||||||
var field = this.state.fields[name];
|
|
||||||
var $th = $('<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 <tr>.
|
|
||||||
* 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 <tr> element that has been rendered.
|
|
||||||
*/
|
|
||||||
_renderRow: function (row) {
|
|
||||||
var self = this;
|
|
||||||
var $tr = $('<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 = $('<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 = $('<td/>', {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 = $('<td>', {
|
|
||||||
'class': tdClassName,
|
|
||||||
'data-form-id': record.id,
|
|
||||||
'data-id': record.data.id,
|
|
||||||
});
|
|
||||||
// We register modifiers on the <td> 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 <td> 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 = this.getParent().mode; // enforce mode of the parent
|
|
||||||
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 $('<tfoot>').append($('<tr>').append('<td/>').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 = $('<td>', {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.
|
* @override
|
||||||
* This function is called everytime the value is changed.
|
*/
|
||||||
*
|
init: function (parent, state, params) {
|
||||||
* @private
|
this._super.apply(this, arguments);
|
||||||
*/
|
this.editable = params.editable;
|
||||||
_computeRowAggregates: function () {
|
this.columns = params.matrix_data.columns;
|
||||||
if (!this.matrix_data.show_row_totals) {
|
this.rows = params.matrix_data.rows;
|
||||||
return;
|
this.matrix_data = params.matrix_data;
|
||||||
}
|
},
|
||||||
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;}
|
|
||||||
|
|
||||||
});
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
return X2Many2dMatrixRenderer;
|
// Display a nice message if there's no data to display
|
||||||
|
this.$el.empty();
|
||||||
|
if (!self.rows.length) {
|
||||||
|
var $alert = $('<div>', {'class': 'alert alert-info'});
|
||||||
|
$alert.text(_t('Sorry no matrix data to display.'));
|
||||||
|
this.$el.append($alert);
|
||||||
|
return this._super();
|
||||||
|
}
|
||||||
|
|
||||||
|
var $table = $('<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 = $('<tbody>').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 = $('<tr>').append('<th/>');
|
||||||
|
$tr = $tr.append(_.map(
|
||||||
|
this.columns,
|
||||||
|
this._renderHeaderCell.bind(this)
|
||||||
|
));
|
||||||
|
if (this.matrix_data.show_row_totals) {
|
||||||
|
$tr.append($('<th/>', {class: 'total'}));
|
||||||
|
}
|
||||||
|
return $('<thead>').append($tr);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a single header cell.
|
||||||
|
*
|
||||||
|
* Creates a th and adds the description as text.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {jQueryElement} node
|
||||||
|
* @returns {jQueryElement} the created <th> node.
|
||||||
|
*/
|
||||||
|
_renderHeaderCell: function (node) {
|
||||||
|
var name = node.attrs.name;
|
||||||
|
var field = this.state.fields[name];
|
||||||
|
var $th = $('<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 <tr>.
|
||||||
|
* 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 <tr> element that has been rendered.
|
||||||
|
*/
|
||||||
|
_renderRow: function (row) {
|
||||||
|
var self = this;
|
||||||
|
var $tr = $('<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
|
||||||
|
* @param {Object} record Contains the information about the record.
|
||||||
|
* @returns {jQueryElement} the cell that was rendered.
|
||||||
|
*/
|
||||||
|
_renderLabelCell: function (record) {
|
||||||
|
var $td = $('<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 = $('<td/>', {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 = $('<td>', {
|
||||||
|
'class': tdClassName,
|
||||||
|
'data-form-id': record.id,
|
||||||
|
'data-id': record.data.id,
|
||||||
|
});
|
||||||
|
// We register modifiers on the <td> 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 <td> 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 $('<tfoot>').append(
|
||||||
|
$('<tr>').append('<td/>').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 = $('<td>', {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 (!_.inArray(type, ['integer', 'float', 'monetary'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_.each(this.columns, function (column, index) {
|
||||||
|
column.aggregate = {
|
||||||
|
fname: fname,
|
||||||
|
ftype: type,
|
||||||
|
help: _t('Sum'),
|
||||||
|
value: 0,
|
||||||
|
};
|
||||||
|
_.each(this.rows, function (row) {
|
||||||
|
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 fname = this.matrix_data.field_value,
|
||||||
|
field = this.state.fields[fname];
|
||||||
|
if (!field) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var type = field.type;
|
||||||
|
if (!_.inArray(type, ['integer', 'float', 'monetary'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_.each(this.rows, function (row) {
|
||||||
|
row.aggregate = {
|
||||||
|
fname: fname,
|
||||||
|
ftype: type,
|
||||||
|
help: _t('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.
|
||||||
|
*
|
||||||
|
* @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});
|
||||||
|
_.each(this.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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* X2many fields expect this
|
||||||
|
*
|
||||||
|
* @returns {null}
|
||||||
|
*/
|
||||||
|
getEditableRecordID: function () {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return X2Many2dMatrixRenderer;
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,177 +4,192 @@
|
||||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
||||||
|
|
||||||
odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
|
odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var core = require('web.core');
|
var field_registry = require('web.field_registry');
|
||||||
// var FieldManagerMixin = require('web.FieldManagerMixin');
|
var relational_fields = require('web.relational_fields');
|
||||||
var field_registry = require('web.field_registry');
|
var X2Many2dMatrixRenderer = require(
|
||||||
var relational_fields = require('web.relational_fields');
|
'web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer'
|
||||||
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({
|
var WidgetX2Many2dMatrix = relational_fields.FieldOne2Many.extend({
|
||||||
widget_class: 'o_form_field_x2many_2d_matrix',
|
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.
|
* Initialize the widget & parameters.
|
||||||
* Sets the axis and the values.
|
*
|
||||||
*/
|
* @param {Object} parent contains the form view.
|
||||||
init_params: function () {
|
* @param {String} name the name of the field.
|
||||||
var node = this.attrs;
|
* @param {Object} record information about the database records.
|
||||||
this.by_x_axis = {};
|
* @param {Object} options view options.
|
||||||
this.by_y_axis = {};
|
*/
|
||||||
this.field_x_axis = node.field_x_axis || this.field_x_axis;
|
init: function (parent, name, record, options) {
|
||||||
this.field_y_axis = node.field_y_axis || this.field_y_axis;
|
this._super(parent, name, record, options);
|
||||||
this.field_label_x_axis = node.field_label_x_axis || this.field_x_axis;
|
this.init_params();
|
||||||
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 specific parameters.
|
||||||
* Initializes the Value matrix.
|
* Sets the axis and the values.
|
||||||
* Puts the values in the grid. If we have related items we use the display name.
|
*/
|
||||||
*/
|
init_params: function () {
|
||||||
init_matrix: function(){
|
var node = this.attrs;
|
||||||
var self = this,
|
this.by_x_axis = {};
|
||||||
records = self.recordData[this.name].data;
|
this.by_y_axis = {};
|
||||||
// Wipe the content if something still exists
|
this.field_x_axis = node.field_x_axis || this.field_x_axis;
|
||||||
this.by_x_axis = {};
|
this.field_y_axis = node.field_y_axis || this.field_y_axis;
|
||||||
this.by_y_axis = {};
|
this.field_label_x_axis =
|
||||||
_.each(records, function(record) {
|
node.field_label_x_axis || this.field_x_axis;
|
||||||
var x = record.data[self.field_x_axis],
|
this.field_label_y_axis =
|
||||||
y = record.data[self.field_y_axis];
|
node.field_label_y_axis || this.field_y_axis;
|
||||||
if (x.type == 'record') {
|
this.x_axis_clickable = this.parse_boolean(
|
||||||
// we have a related record
|
node.x_axis_clickable || '1'
|
||||||
x = x.data.display_name;
|
);
|
||||||
}
|
this.y_axis_clickable = this.parse_boolean(
|
||||||
if (y.type == 'record') {
|
node.y_axis_clickable || '1'
|
||||||
// we have a related record
|
);
|
||||||
y = y.data.display_name;
|
this.field_value = node.field_value || this.field_value;
|
||||||
}
|
// TODO: is this really needed? Holger?
|
||||||
self.by_x_axis[x] = self.by_x_axis[x] || {};
|
for (var property in node) {
|
||||||
self.by_y_axis[y] = self.by_y_axis[y] || {};
|
if (property.startsWith("field_att_")) {
|
||||||
self.by_x_axis[x][y] = record;
|
this.fields_att[property.substring(10)] =
|
||||||
self.by_y_axis[y][x] = record;
|
node[property];
|
||||||
});
|
}
|
||||||
// init columns
|
}
|
||||||
self.columns = [];
|
// and this?
|
||||||
$.each(self.by_x_axis, function(x){
|
this.field_editability =
|
||||||
self.columns.push(self._make_column(x));
|
node.field_editability || this.field_editability;
|
||||||
});
|
this.show_row_totals =
|
||||||
self.rows = [];
|
this.parse_boolean(node.show_row_totals || '1');
|
||||||
$.each(self.by_y_axis, function(y){
|
this.show_column_totals =
|
||||||
self.rows.push(self._make_row(y));
|
this.parse_boolean(node.show_column_totals || '1');
|
||||||
});
|
},
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
},
|
/**
|
||||||
/**
|
* Initializes the Value matrix.
|
||||||
* Create scaffold for a column.
|
*
|
||||||
*
|
* Puts the values in the grid.
|
||||||
* @params {String} x: The string used as a column title
|
* If we have related items we use the display name.
|
||||||
*/
|
*/
|
||||||
_make_column: function(x){
|
init_matrix: function () {
|
||||||
return {
|
var records = this.recordData[this.name].data;
|
||||||
// simulate node parsed on xml arch
|
// Wipe the content if something still exists
|
||||||
'tag': 'field',
|
this.by_x_axis = {};
|
||||||
'attrs': {
|
this.by_y_axis = {};
|
||||||
'name': this.field_x_axis,
|
_.each(records, function (record) {
|
||||||
'string': x
|
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;
|
||||||
* Create scaffold for a row.
|
}
|
||||||
*
|
if (y.type === 'record') {
|
||||||
* @params {String} x: The string used as a row title
|
// we have a related record
|
||||||
*/
|
y = y.data.display_name;
|
||||||
_make_row: function(y){
|
}
|
||||||
var self = this;
|
this.by_x_axis[x] = this.by_x_axis[x] || {};
|
||||||
// use object so that we can attach more data if needed
|
this.by_y_axis[y] = this.by_y_axis[y] || {};
|
||||||
var row = {'data': []};
|
this.by_x_axis[x][y] = record;
|
||||||
$.each(self.by_x_axis, function(x) {
|
this.by_y_axis[y][x] = record;
|
||||||
row.data.push(self.by_y_axis[y][x]);
|
}.bind(this));
|
||||||
});
|
// init columns
|
||||||
return row;
|
this.columns = [];
|
||||||
},
|
$.each(this.by_x_axis, function (x) {
|
||||||
/**
|
this.columns.push(this._make_column(x));
|
||||||
*Parse a String containing a Python bool or 1 and convert it to a proper bool.
|
}.bind(this));
|
||||||
*
|
this.rows = [];
|
||||||
* @params {String} val: the string to be parsed.
|
$.each(this.by_y_axis, function (y) {
|
||||||
* @returns {Boolean} The parsed boolean.
|
this.rows.push(this._make_row(y));
|
||||||
*/
|
}.bind(this));
|
||||||
parse_boolean: function(val) {
|
this.matrix_data = {
|
||||||
if (val.toLowerCase() === 'true' || val === '1') {
|
'field_value': this.field_value,
|
||||||
return true;
|
'field_x_axis': this.field_x_axis,
|
||||||
}
|
'field_y_axis': this.field_y_axis,
|
||||||
return false;
|
'columns': this.columns,
|
||||||
},
|
'rows': this.rows,
|
||||||
/**
|
'show_row_totals': this.show_row_totals,
|
||||||
*Create the matrix renderer and add its output to our element
|
'show_column_totals': this.show_column_totals,
|
||||||
*
|
};
|
||||||
* @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);
|
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* 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,
|
||||||
|
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);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
field_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix);
|
||||||
|
|
||||||
|
return {
|
||||||
|
WidgetX2Many2dMatrix: WidgetX2Many2dMatrix,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue