mirror of https://github.com/OCA/web.git
Merge pull request #96 from hbrunn/8.0-web_widget_x2many_2d_matrix
8.0 web widget x2many 2d matrixpull/138/head
commit
67356eea8a
|
@ -0,0 +1,78 @@
|
||||||
|
2D matrix for x2many fields
|
||||||
|
===========================
|
||||||
|
|
||||||
|
This module allows to show an x2many field with 3-tuples
|
||||||
|
($x_value, $y_value, $value) in a table
|
||||||
|
|
||||||
|
$x_value1 $x_value2
|
||||||
|
========= =========== ===========
|
||||||
|
$y_value1 $value(1/1) $value(2/1)
|
||||||
|
$y_value2 $value(1/2) $value(2/2)
|
||||||
|
========= =========== ===========
|
||||||
|
|
||||||
|
where `value(n/n)` is editable.
|
||||||
|
|
||||||
|
An example use case would be: Select some projects and some employees so that
|
||||||
|
a manager can easily fill in the planned_hours for one task per employee. The
|
||||||
|
result could look like this:
|
||||||
|
|
||||||
|
.. image:: /web_widget_x2many_2d_matrix/static/description/screenshot.png
|
||||||
|
:alt: Screenshot
|
||||||
|
|
||||||
|
The beauty of this is that you have an arbitrary amount of columns with this widget, trying to get this in standard x2many lists involves some quite agly hacks.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
Use this widget by saying::
|
||||||
|
|
||||||
|
<field name="my_field" widget="x2many_2d_matrix" />
|
||||||
|
|
||||||
|
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::
|
||||||
|
|
||||||
|
<field name="my_field" widget="x2many_2d_matrix" field_x_axis="my_field1" field_y_axis="my_field2" field_value="my_field3" />
|
||||||
|
|
||||||
|
You can pass the following parameters:
|
||||||
|
|
||||||
|
field_x_axis
|
||||||
|
The field that indicates the x value of a point
|
||||||
|
field_y_axis
|
||||||
|
The field that indicates the y value of a point
|
||||||
|
field_label_x_axis
|
||||||
|
Use another field to display in the table header
|
||||||
|
field_label_y_axis
|
||||||
|
Use another field to display in the table header
|
||||||
|
field_value
|
||||||
|
Show this field as value
|
||||||
|
show_row_totals
|
||||||
|
If field_value is a numeric field, calculate row totals
|
||||||
|
show_column_totals
|
||||||
|
If field_value is a numeric field, calculate column totals
|
||||||
|
|
||||||
|
Known issues / Roadmap
|
||||||
|
======================
|
||||||
|
|
||||||
|
* it would be worth trying to instantiate the proper field widget and let it render the input
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Holger Brunn <hbrunn@therp.nl>
|
||||||
|
|
||||||
|
Maintainer
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. image:: http://odoo-community.org/logo.png
|
||||||
|
:alt: Odoo Community Association
|
||||||
|
:target: http://odoo-community.org
|
||||||
|
|
||||||
|
This module is maintained by the OCA.
|
||||||
|
|
||||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
|
||||||
|
|
||||||
|
To contribute to this module, please visit http://odoo-community.org.
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# This module copyright (C) 2015 Therp BV <http://therp.nl>.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
|
@ -0,0 +1,45 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# This module copyright (C) 2015 Therp BV <http://therp.nl>.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
{
|
||||||
|
"name": "2D matrix for x2many fields",
|
||||||
|
"version": "1.0",
|
||||||
|
"author": "Therp BV",
|
||||||
|
"license": "AGPL-3",
|
||||||
|
"category": "Hidden/Dependency",
|
||||||
|
"summary": "Show list fields as a matrix",
|
||||||
|
"depends": [
|
||||||
|
'web',
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
'views/templates.xml',
|
||||||
|
],
|
||||||
|
"qweb": [
|
||||||
|
'static/src/xml/web_widget_x2many_2d_matrix.xml',
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
],
|
||||||
|
"auto_install": False,
|
||||||
|
"installable": True,
|
||||||
|
"application": False,
|
||||||
|
"external_dependencies": {
|
||||||
|
'python': [],
|
||||||
|
},
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
|
@ -0,0 +1,8 @@
|
||||||
|
.oe_form_field_x2many_2d_matrix th.oe_link
|
||||||
|
{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.openerp .oe_form_field_x2many_2d_matrix .oe_list_content > tbody > tr > td.oe_list_field_cell
|
||||||
|
{
|
||||||
|
white-space: normal;
|
||||||
|
}
|
|
@ -0,0 +1,380 @@
|
||||||
|
//-*- coding: utf-8 -*-
|
||||||
|
//############################################################################
|
||||||
|
//
|
||||||
|
// OpenERP, Open Source Management Solution
|
||||||
|
// This module copyright (C) 2015 Therp BV <http://therp.nl>.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
//############################################################################
|
||||||
|
|
||||||
|
openerp.web_widget_x2many_2d_matrix = function(instance)
|
||||||
|
{
|
||||||
|
instance.web.form.widgets.add(
|
||||||
|
'x2many_2d_matrix',
|
||||||
|
'instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix');
|
||||||
|
instance.web_widget_x2many_2d_matrix.FieldX2Many2dMatrix = instance.web.form.FieldOne2Many.extend({
|
||||||
|
template: 'FieldX2Many2dMatrix',
|
||||||
|
widget_class: 'oe_form_field_x2many_2d_matrix',
|
||||||
|
|
||||||
|
// those will be filled with rows from the dataset
|
||||||
|
by_x_axis: {},
|
||||||
|
by_y_axis: {},
|
||||||
|
field_x_axis: 'x',
|
||||||
|
field_label_x_axis: 'x',
|
||||||
|
field_y_axis: 'y',
|
||||||
|
field_label_y_axis: 'y',
|
||||||
|
field_value: 'value',
|
||||||
|
// information about our datatype
|
||||||
|
is_numeric: false,
|
||||||
|
show_row_totals: true,
|
||||||
|
show_column_totals: true,
|
||||||
|
// this will be filled with the model's fields_get
|
||||||
|
fields: {},
|
||||||
|
|
||||||
|
// read parameters
|
||||||
|
init: function(field_manager, node)
|
||||||
|
{
|
||||||
|
this.field_x_axis = node.attrs.field_x_axis || this.field_x_axis;
|
||||||
|
this.field_y_axis = node.attrs.field_y_axis || this.field_y_axis;
|
||||||
|
this.field_label_x_axis = node.attrs.field_label_x_axis || this.field_x_axis;
|
||||||
|
this.field_label_y_axis = node.attrs.field_label_y_axis || this.field_y_axis;
|
||||||
|
this.field_value = node.attrs.field_value || this.field_value;
|
||||||
|
this.show_row_totals = node.attrs.show_row_totals || this.show_row_totals;
|
||||||
|
this.show_column_totals = node.attrs.show_column_totals || this.show_column_totals;
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
// return a field's value, id in case it's a one2many field
|
||||||
|
get_field_value: function(row, field, many2one_as_name)
|
||||||
|
{
|
||||||
|
if(this.fields[field].type == 'many2one' && _.isArray(row[field]))
|
||||||
|
{
|
||||||
|
if(many2one_as_name)
|
||||||
|
{
|
||||||
|
return row[field][1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return row[field][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return row[field];
|
||||||
|
},
|
||||||
|
|
||||||
|
// setup our datastructure for simple access in the template
|
||||||
|
set_value: function()
|
||||||
|
{
|
||||||
|
var self = this,
|
||||||
|
result = this._super.apply(this, arguments);
|
||||||
|
|
||||||
|
self.by_x_axis = {};
|
||||||
|
self.by_y_axis = {};
|
||||||
|
|
||||||
|
return jQuery.when(result).then(function()
|
||||||
|
{
|
||||||
|
return self.dataset._model.call('fields_get').then(function(fields)
|
||||||
|
{
|
||||||
|
self.fields = fields;
|
||||||
|
self.is_numeric = fields[self.field_value].type == 'float';
|
||||||
|
self.show_row_totals &= self.is_numeric;
|
||||||
|
self.show_column_totals &= self.is_numeric;
|
||||||
|
}).then(function()
|
||||||
|
{
|
||||||
|
return self.dataset.read_ids(self.dataset.ids).then(function(rows)
|
||||||
|
{
|
||||||
|
var read_many2one = {},
|
||||||
|
many2one_fields = [
|
||||||
|
self.field_x_axis, self.field_y_axis,
|
||||||
|
self.field_label_x_axis, self.field_label_y_axis
|
||||||
|
];
|
||||||
|
// prepare to read many2one names if necessary (we can get (id, name) or just id as value)
|
||||||
|
_.each(many2one_fields, function(field)
|
||||||
|
{
|
||||||
|
if(self.fields[field].type == 'many2one')
|
||||||
|
{
|
||||||
|
read_many2one[field] = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// setup data structure
|
||||||
|
_.each(rows, function(row)
|
||||||
|
{
|
||||||
|
self.add_xy_row(row);
|
||||||
|
_.each(read_many2one, function(rows, field)
|
||||||
|
{
|
||||||
|
if(!_.isArray(row[field]))
|
||||||
|
{
|
||||||
|
rows[row[field]] = rows[row[field]] || []
|
||||||
|
rows[row[field]].push(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// read many2one fields if necessary
|
||||||
|
var deferrends = [];
|
||||||
|
_.each(read_many2one, function(rows, field)
|
||||||
|
{
|
||||||
|
if(_.isEmpty(rows))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var model = new instance.web.Model(self.fields[field].relation);
|
||||||
|
deferrends.push(model.call(
|
||||||
|
'name_get',
|
||||||
|
[_.map(_.keys(rows), function(key) {return parseInt(key)})])
|
||||||
|
.then(function(names)
|
||||||
|
{
|
||||||
|
_.each(names, function(name)
|
||||||
|
{
|
||||||
|
_.each(rows[name[0]], function(row)
|
||||||
|
{
|
||||||
|
row[field] = name;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
if(self.is_started && !self.no_rerender)
|
||||||
|
{
|
||||||
|
self.renderElement();
|
||||||
|
self.compute_totals();
|
||||||
|
self.setup_many2one_axes();
|
||||||
|
self.$el.find('.edit').on(
|
||||||
|
'change', self.proxy(self.xy_value_change));
|
||||||
|
self.effective_readonly_change();
|
||||||
|
}
|
||||||
|
return jQuery.when.apply(jQuery, deferrends);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// to whatever needed to setup internal data structure
|
||||||
|
add_xy_row: function(row)
|
||||||
|
{
|
||||||
|
var x = this.get_field_value(row, this.field_x_axis),
|
||||||
|
y = this.get_field_value(row, this.field_y_axis);
|
||||||
|
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] = row;
|
||||||
|
this.by_y_axis[y][x] = row;
|
||||||
|
},
|
||||||
|
|
||||||
|
// get x axis values in the correct order
|
||||||
|
get_x_axis_values: function()
|
||||||
|
{
|
||||||
|
return _.keys(this.by_x_axis);
|
||||||
|
},
|
||||||
|
|
||||||
|
// get y axis values in the correct order
|
||||||
|
get_y_axis_values: function()
|
||||||
|
{
|
||||||
|
return _.keys(this.by_y_axis);
|
||||||
|
},
|
||||||
|
|
||||||
|
// get the label for a value on the x axis
|
||||||
|
get_x_axis_label: function(x)
|
||||||
|
{
|
||||||
|
return this.get_field_value(
|
||||||
|
_.first(_.values(this.by_x_axis[x])),
|
||||||
|
this.field_label_x_axis, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
// get the label for a value on the y axis
|
||||||
|
get_y_axis_label: function(y)
|
||||||
|
{
|
||||||
|
return this.get_field_value(
|
||||||
|
_.first(_.values(this.by_y_axis[y])),
|
||||||
|
this.field_label_y_axis, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
// return the class(es) the inputs should have
|
||||||
|
get_xy_value_class: function()
|
||||||
|
{
|
||||||
|
var classes = 'oe_form_field oe_form_required';
|
||||||
|
if(this.is_numeric)
|
||||||
|
{
|
||||||
|
classes += ' oe_form_field_float';
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
},
|
||||||
|
|
||||||
|
// return row id of a coordinate
|
||||||
|
get_xy_id: function(x, y)
|
||||||
|
{
|
||||||
|
return this.by_x_axis[x][y]['id'];
|
||||||
|
},
|
||||||
|
|
||||||
|
// return the value of a coordinate
|
||||||
|
get_xy_value: function(x, y)
|
||||||
|
{
|
||||||
|
return this.get_field_value(
|
||||||
|
this.by_x_axis[x][y], this.field_value);
|
||||||
|
},
|
||||||
|
|
||||||
|
// validate a value
|
||||||
|
validate_xy_value: function(val)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.parse_xy_value(val);
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
// parse a value from user input
|
||||||
|
parse_xy_value: function(val)
|
||||||
|
{
|
||||||
|
return instance.web.parse_value(
|
||||||
|
val, {'type': this.fields[this.field_value].type});
|
||||||
|
},
|
||||||
|
|
||||||
|
// format a value from the database for display
|
||||||
|
format_xy_value: function(val)
|
||||||
|
{
|
||||||
|
return instance.web.format_value(
|
||||||
|
val, {'type': this.fields[this.field_value].type});
|
||||||
|
},
|
||||||
|
|
||||||
|
// compute totals
|
||||||
|
compute_totals: function()
|
||||||
|
{
|
||||||
|
var self = this,
|
||||||
|
grand_total = 0,
|
||||||
|
totals_x = {},
|
||||||
|
totals_y = {};
|
||||||
|
return self.dataset.read_ids(self.dataset.ids).then(function(rows)
|
||||||
|
{
|
||||||
|
_.each(rows, function(row)
|
||||||
|
{
|
||||||
|
var key_x = self.get_field_value(row, self.field_x_axis),
|
||||||
|
key_y = self.get_field_value(row, self.field_y_axis);
|
||||||
|
totals_x[key_x] = (totals_x[key_x] || 0) + self.get_field_value(row, self.field_value);
|
||||||
|
totals_y[key_y] = (totals_y[key_y] || 0) + self.get_field_value(row, self.field_value);
|
||||||
|
grand_total += self.get_field_value(row, self.field_value);
|
||||||
|
});
|
||||||
|
}).then(function()
|
||||||
|
{
|
||||||
|
_.each(totals_y, function(total, y)
|
||||||
|
{
|
||||||
|
self.$el.find(
|
||||||
|
_.str.sprintf('td.row_total[data-y="%s"]', y)).text(
|
||||||
|
self.format_xy_value(total));
|
||||||
|
});
|
||||||
|
_.each(totals_x, function(total, x)
|
||||||
|
{
|
||||||
|
self.$el.find(
|
||||||
|
_.str.sprintf('td.column_total[data-x="%s"]', x)).text(
|
||||||
|
self.format_xy_value(total));
|
||||||
|
});
|
||||||
|
self.$el.find('.grand_total').text(
|
||||||
|
self.format_xy_value(grand_total))
|
||||||
|
return {
|
||||||
|
totals_x: totals_x,
|
||||||
|
totals_y: totals_y,
|
||||||
|
grand_total: grand_total,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setup_many2one_axes: function()
|
||||||
|
{
|
||||||
|
if(this.fields[this.field_x_axis].type == 'many2one')
|
||||||
|
{
|
||||||
|
this.$el.find('th[data-x]').addClass('oe_link')
|
||||||
|
.click(_.partial(
|
||||||
|
this.proxy(this.many2one_axis_click),
|
||||||
|
this.field_x_axis, 'x'));
|
||||||
|
}
|
||||||
|
if(this.fields[this.field_y_axis].type == 'many2one')
|
||||||
|
{
|
||||||
|
this.$el.find('tr[data-y] th').addClass('oe_link')
|
||||||
|
.click(_.partial(
|
||||||
|
this.proxy(this.many2one_axis_click),
|
||||||
|
this.field_y_axis, 'y'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
many2one_axis_click: function(field, id_attribute, e)
|
||||||
|
{
|
||||||
|
this.do_action({
|
||||||
|
type: 'ir.actions.act_window',
|
||||||
|
name: this.fields[field].string,
|
||||||
|
res_model: this.fields[field].relation,
|
||||||
|
res_id: jQuery(e.currentTarget).data(id_attribute),
|
||||||
|
views: [[false, 'form']],
|
||||||
|
target: 'current',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
start: function()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
this.$el.find('.edit').on(
|
||||||
|
'change', self.proxy(this.xy_value_change));
|
||||||
|
this.compute_totals();
|
||||||
|
this.setup_many2one_axes();
|
||||||
|
this.on("change:effective_readonly",
|
||||||
|
this, this.proxy(this.effective_readonly_change));
|
||||||
|
this.effective_readonly_change();
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
xy_value_change: function(e)
|
||||||
|
{
|
||||||
|
var $this = jQuery(e.currentTarget),
|
||||||
|
val = $this.val();
|
||||||
|
if(this.validate_xy_value(val))
|
||||||
|
{
|
||||||
|
var data = {}, value = this.parse_xy_value(val);
|
||||||
|
data[this.field_value] = value;
|
||||||
|
|
||||||
|
$this.siblings('.read').text(this.format_xy_value(value));
|
||||||
|
$this.val(this.format_xy_value(value));
|
||||||
|
|
||||||
|
this.dataset.write($this.data('id'), data);
|
||||||
|
$this.parent().removeClass('oe_form_invalid');
|
||||||
|
this.compute_totals();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this.parent().addClass('oe_form_invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
effective_readonly_change: function()
|
||||||
|
{
|
||||||
|
this.$el
|
||||||
|
.find('tbody td.oe_list_field_cell span.oe_form_field .edit')
|
||||||
|
.toggle(!this.get('effective_readonly'));
|
||||||
|
this.$el
|
||||||
|
.find('tbody td.oe_list_field_cell span.oe_form_field .read')
|
||||||
|
.toggle(this.get('effective_readonly'));
|
||||||
|
this.$el.find('.edit').first().focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
is_syntax_valid: function()
|
||||||
|
{
|
||||||
|
return this.$el.find('.oe_form_invalid').length == 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
// deactivate view related functions
|
||||||
|
load_views: function() {},
|
||||||
|
reload_current_view: function() {},
|
||||||
|
get_active_view: function() {},
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<templates>
|
||||||
|
<t t-name="FieldX2Many2dMatrix">
|
||||||
|
<div t-att-class="widget.widget_class">
|
||||||
|
<table class="oe_list_content">
|
||||||
|
<thead>
|
||||||
|
<tr class="oe_list_header_columns">
|
||||||
|
<th />
|
||||||
|
<th t-foreach="widget.get_x_axis_values()" t-as="x" t-att-data-x="x">
|
||||||
|
<t t-esc="widget.get_x_axis_label(x)" />
|
||||||
|
</th>
|
||||||
|
<th t-if="widget.show_row_totals">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="widget.get_y_axis_values()" t-as="y" t-att-data-y="y">
|
||||||
|
<th><t t-esc="widget.get_y_axis_label(y)" /></th>
|
||||||
|
<td t-foreach="widget.get_x_axis_values()" t-as="x" t-att-class="'oe_list_field_cell' + (widget.is_numeric ? ' oe_number' : '')" t-att-data-x="x">
|
||||||
|
<span t-att-class="widget.get_xy_value_class()">
|
||||||
|
<input class="edit" t-att-data-id="widget.get_xy_id(x, y)" t-att-value="widget.format_xy_value(widget.get_xy_value(x, y))" />
|
||||||
|
<span class="read"><t t-esc="widget.format_xy_value(widget.get_xy_value(x, y))" /></span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td t-if="widget.show_row_totals" class="row_total oe_number" t-att-data-y="y"/>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot t-if="widget.show_column_totals">
|
||||||
|
<tr>
|
||||||
|
<th>Total</th>
|
||||||
|
<td t-foreach="widget.get_x_axis_values()" t-as="x" class="oe_list_footer oe_number column_total" t-att-data-x="x" />
|
||||||
|
<td class="grand_total oe_number" />
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<template id="assets_backend" name="web_widget_x2many_2d_matrix assets" inherit_id="web.assets_backend">
|
||||||
|
<xpath expr="." position="inside">
|
||||||
|
<script type="text/javascript" src="/web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js"></script>
|
||||||
|
<link rel="stylesheet" href="/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css"/>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
</data>
|
||||||
|
</openerp>
|
Loading…
Reference in New Issue