Browse Source

Merge pull request #96 from hbrunn/8.0-web_widget_x2many_2d_matrix

8.0 web widget x2many 2d matrix
pull/138/head
Sylvain LE GAL 10 years ago
parent
commit
67356eea8a
  1. 78
      web_widget_x2many_2d_matrix/README.rst
  2. 20
      web_widget_x2many_2d_matrix/__init__.py
  3. 45
      web_widget_x2many_2d_matrix/__openerp__.py
  4. BIN
      web_widget_x2many_2d_matrix/static/description/icon.png
  5. BIN
      web_widget_x2many_2d_matrix/static/description/screenshot.png
  6. 8
      web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css
  7. 380
      web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js
  8. 36
      web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml
  9. 11
      web_widget_x2many_2d_matrix/views/templates.xml

78
web_widget_x2many_2d_matrix/README.rst

@ -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.

20
web_widget_x2many_2d_matrix/__init__.py

@ -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/>.
#
##############################################################################

45
web_widget_x2many_2d_matrix/__openerp__.py

@ -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': [],
},
}

BIN
web_widget_x2many_2d_matrix/static/description/icon.png

After

Width: 80  |  Height: 80  |  Size: 5.0 KiB

BIN
web_widget_x2many_2d_matrix/static/description/screenshot.png

After

Width: 914  |  Height: 349  |  Size: 19 KiB

8
web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css

@ -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;
}

380
web_widget_x2many_2d_matrix/static/src/js/web_widget_x2many_2d_matrix.js

@ -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() {},
});
}

36
web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml

@ -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>

11
web_widget_x2many_2d_matrix/views/templates.xml

@ -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…
Cancel
Save