Add sum of records on list view

issue6947
review35771002
This commit is contained in:
Nicolas Évrard
2018-03-16 18:48:05 +01:00
parent 03bdc638e9
commit 9614d8bac6
5 changed files with 192 additions and 34 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add sum of records on list view
* Add shortcuts to Date and DateTime widgets
* Use limit to expand tree
* Use spellcheck and lang attributes for textbox widget
+10
View File
@@ -3353,4 +3353,14 @@
var ellipsis = Sao.i18n.gettext('...');
return string.slice(0, length - ellipsis.length) + ellipsis;
};
Sao.common.debounce = function(func, wait) {
return function() {
var args = [].slice(arguments);
clearTimeout(func._debounceTimeout);
func._debounceTimeout = setTimeout(function() {
func.apply(this, args);
}.bind(this), wait);
}.bind(this);
};
}());
+1
View File
@@ -464,6 +464,7 @@ var Sao = {};
init: function(favorite) {
this.field = favorite;
this.header = jQuery('<th/>');
this.footers = [];
this.attributes = jQuery.extend({}, this.field.description);
this.attributes.name = this.field.name;
+19 -6
View File
@@ -120,6 +120,10 @@ img.icon {
width: 1em;
height: 1em;
}
& tr.more-row {
text-align: center;
}
}
& .editabletree-char, .editabletree-date, .editabletree-datetime,
.editabletree-integer, .editabletree-float, .editabletree-selection,
@@ -137,9 +141,6 @@ img.icon {
& .expander {
cursor: pointer;
}
& .treefooter {
text-align: center;
}
& label {
font-weight: normal;
display: inline;
@@ -324,7 +325,10 @@ input.column-boolean {
table.responsive > thead > tr > th:not(.invisible),
table.responsive > tbody,
table.responsive > tbody > tr,
table.responsive > tbody > tr > td:not(.invisible) {
table.responsive > tbody > tr > td:not(.invisible),
table.responsive > tfoot,
table.responsive > tfoot > tr,
table.responsive > tfoot > tr > td:not(.invisible) {
display: block !important;
}
table.responsive {
@@ -343,6 +347,13 @@ input.column-boolean {
display: block;
}
}
& tfoot {
> tr > td:empty,
> tr > td > label {
display: none !important;
}
}
}
table.responsive-noheader {
& > tbody > tr > td {
@@ -353,13 +364,15 @@ input.column-boolean {
> tbody > tr {
border: 1px solid #ccc;
}
& > tbody > tr > td {
& > tbody > tr > td,
& > tfoot > tr > td {
/* Behave like a "row" */
border: none;
border-bottom: 1px solid #eee;
position: relative;
}
& > tbody > tr {
& > tbody > tr,
& > tfoot > tr {
& > td:before {
/* Now like a table header */
position: absolute;
+161 -28
View File
@@ -53,6 +53,7 @@
init: function(screen, xml, children_field) {
Sao.View.Tree._super.init.call(this, screen, xml);
this.view_type = 'tree';
this.sum_widgets = {};
this.selection_mode = (screen.attributes.selection_mode ||
Sao.common.SELECTION_MULTIPLE);
this.el = jQuery('<div/>', {
@@ -88,6 +89,17 @@
th.append(this.selection);
tr.append(th);
this.thead.append(tr);
this.tfoot = null;
var sum_row;
if (!jQuery.isEmptyObject(this.sum_widgets)) {
sum_row = jQuery('<tr/>');
sum_row.append(jQuery('<td/>'));
this.tfoot = jQuery('<tfoot/>');
this.tfoot.append(sum_row);
this.table.append(this.tfoot);
}
this.columns.forEach(function(column) {
th = jQuery('<th/>', {
'class': column.attributes.widget,
@@ -116,26 +128,28 @@
}
tr.append(th.append(label));
column.header = th;
column.footers = [];
if (!jQuery.isEmptyObject(this.sum_widgets)) {
var field_name = column.attributes.name;
var total_cell = jQuery('<td/>', {
'class': column.class_,
});
if (field_name in this.sum_widgets) {
var sum_label = this.sum_widgets[field_name][0];
var sum_div = this.sum_widgets[field_name][1];
total_cell.append(label);
total_cell.append(sum_div);
total_cell.attr('data-title', label.text());
}
sum_row.append(total_cell);
column.footers.push(total_cell);
}
}, this);
this.tbody = jQuery('<tbody/>');
this.table.append(this.tbody);
// Footer for more
var footer = jQuery('<div/>', {
'class': 'treefooter'
});
this.more = jQuery('<button/>', {
'class': 'btn btn-default',
'type': 'button'
}).append(Sao.i18n.gettext('More')
).click(function() {
this.display_size += Sao.config.display_size;
this.display();
}.bind(this));
footer.append(this.more);
this.more.hide();
this.display_size = Sao.config.display_size;
this.el.append(footer);
},
create_columns: function(model, xml) {
xml.find('tree').children().each(function(pos, child) {
@@ -211,7 +225,7 @@
column.sortable = true;
}
this.fields[name] = true;
// TODO sum
this.add_sum(attributes);
} else if (child.tagName == 'button') {
column = new Sao.View.Tree.ButtonColumn(this.screen,
attributes);
@@ -219,6 +233,19 @@
this.columns.push(column);
}.bind(this));
},
add_sum: function(attributes){
if (!attributes.sum) {
return;
}
var sum = jQuery('<label/>', {
text: attributes.sum,
'data-title': attributes.sum + Sao.i18n.gettext(': ')
});
var aggregate = jQuery('<span/>', {
'class': 'value',
});
this.sum_widgets[attributes.name] = [sum, aggregate];
},
sort_model: function(e){
var column = e.data;
var arrow = column.arrow;
@@ -314,6 +341,7 @@
}
// Set column visibility depending on attributes and domain
var visible_columns = 1; // start at 1 because of the checkbox
var domain = [];
if (!jQuery.isEmptyObject(this.screen.domain)) {
domain.push(this.screen.domain);
@@ -326,14 +354,20 @@
domain = inversion.simplify(domain);
var decoder = new Sao.PYSON.Decoder(this.screen.context());
this.columns.forEach(function(column) {
visible_columns += 1;
var name = column.attributes.name;
if (!name) {
return;
}
var related_cells = column.footers.slice();
related_cells.push(column.header);
if ((decoder.decode(column.attributes.tree_invisible || '0')) ||
(name === this.screen.exclude_field)) {
column.header.hide();
column.header.addClass('invisible');
visible_columns -= 1;
related_cells.forEach(function(cell) {
cell.hide();
cell.addClass('invisible');
});
} else {
var inv_domain = inversion.domain_inversion(domain, name);
if (typeof inv_domain != 'boolean') {
@@ -341,14 +375,21 @@
}
var unique = inversion.unique_value(inv_domain)[0];
if (unique && jQuery.isEmptyObject(this.children_field)) {
column.header.hide();
column.header.addClass('invisible');
visible_columns -= 1;
related_cells.forEach(function(cell) {
cell.hide();
cell.addClass('invisible');
});
} else {
column.header.show();
column.header.removeClass('invisible');
related_cells.forEach(function(cell) {
cell.show();
cell.removeClass('invisible');
});
}
}
}.bind(this));
this.tbody.find('tr.more-row > td').attr(
'colspan', visible_columns);
if (this.columns.filter(function(c) {
return c.header.is(':visible');
@@ -360,13 +401,16 @@
this.table.removeClass('responsive-header');
}
return this.redraw(selected, expanded);
return this.redraw(selected, expanded).done(
Sao.common.debounce(this.update_sum.bind(this), 250));
},
construct: function(selected, expanded, extend) {
var tbody = this.tbody;
if (!extend) {
this.rows = [];
this.tbody = jQuery('<tbody/>');
} else {
this.tbody.find('tr.more-row').remove();
}
var start = this.rows.length;
var add_row = function(record, pos, group) {
@@ -382,14 +426,27 @@
};
this.screen.group.slice(start, this.display_size).forEach(
add_row.bind(this));
if (this.display_size >= this.screen.group.length) {
this.more.hide();
} else {
this.more.show();
}
if (!extend) {
tbody.replaceWith(this.tbody);
}
if (this.display_size < this.screen.group.length) {
var more_row = jQuery('<tr/>', {
'class': 'more-row',
});
var more_cell = jQuery('<td/>');
var more_button = jQuery('<button/>', {
'class': 'btn btn-default',
'type': 'button'
}).append(Sao.i18n.gettext('More')
).click(function() {
this.display_size += Sao.config.display_size;
this.display();
}.bind(this));
more_cell.append(more_button);
more_row.append(more_cell);
this.tbody.append(more_row);
}
},
redraw: function(selected, expanded) {
return redraw_async(this.rows, selected, expanded);
@@ -425,6 +482,79 @@
}
// TODO update_children
},
update_sum: function() {
for (var name in this.sum_widgets) {
if (!this.sum_widgets.hasOwnProperty(name)) {
continue;
}
var selected_records = this.selected_records();
var aggregate = '-';
var aggregate_el = this.sum_widgets[name][1];
var sum_ = null;
var selected_sum = null;
var loaded = true;
var digit = 0;
var field = this.screen.model.fields[name];
var i, record;
var records_ids = selected_records.map(function(record){
return record.id;
});
for (i=0; i < this.screen.group.length; i++){
record = this.screen.group[i];
if (!record.get_loaded([name]) && record.id >=0){
loaded = false;
break;
}
var value = field.get(record);
if (value && value.isTimeDelta) {
value = value.asSeconds();
}
if (value !== null){
if (sum_ === null){
sum_ = value;
}else {
sum_ += value;
}
if (~records_ids.indexOf(record.id) ||
!selected_records){
if (selected_sum === null){
selected_sum = value;
}else {
selected_sum += value;
}
}
if (field.digits) {
var fdigits = field.digits(record);
if (fdigits && digit !== null){
digit = Math.max(fdigits[1], digit);
} else {
digit = null;
}
}
}
}
if (loaded) {
if (field.description.type == 'timedelta'){
var converter = field.converter(record.group);
selected_sum = Sao.common.timedelta.format(
Sao.TimeDelta(null, selected_sum), converter);
sum_ = Sao.common.timedelta.format(
Sao.TimeDelta(null, sum_), converter);
} else if (digit !== null){
var options = {};
options.minimumFractionDigits = digit;
options.maximumFractionDigits = digit;
selected_sum = (selected_sum || 0).toLocaleString(
Sao.i18n.getlang(), options);
sum_ = (sum_ || 0).toLocaleString(
Sao.i18n.getlang(), options);
}
aggregate = selected_sum + ' / ' + sum_;
}
aggregate_el.text(aggregate);
}
},
selected_records: function() {
if (this.selection_mode == Sao.common.SELECTION_NONE) {
return [];
@@ -458,8 +588,10 @@
} else {
this.select_changed(null);
}
this.update_sum();
},
update_selection: function() {
this.update_sum();
if (this.selection.prop('checked')) {
return;
}
@@ -1393,6 +1525,7 @@
this.prefixes = [];
this.suffixes = [];
this.header = null;
this.footers = [];
},
get_cell: function() {
var cell = jQuery('<div/>', {