449 lines
13 KiB
JavaScript
449 lines
13 KiB
JavaScript
|
|
/* jshint undef: false */
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
openerp.web_graph.PivotTable = openerp.web.Class.extend({
|
|
init: function (options) {
|
|
this.rows = { groupby: options.row_groupby };
|
|
this.cols = { groupby: [] };
|
|
this.cells = [];
|
|
this.model = options.model;
|
|
this.domain = options.domain;
|
|
this.measure = options.measure;
|
|
this.measure_label = options.measure_label;
|
|
this.total = 0;
|
|
this.id_seed = 0;
|
|
},
|
|
|
|
start: function () {
|
|
return this.expand_all();
|
|
},
|
|
|
|
generate_id: function () {
|
|
this.id_seed += 1;
|
|
return this.id_seed;
|
|
},
|
|
|
|
visible_fields: function () {
|
|
return this.rows.groupby.concat(this.cols.groupby, this.measure);
|
|
},
|
|
|
|
set_value: function (id1, id2, value) {
|
|
var x = Math.min(id1, id2),
|
|
y = Math.max(id1, id2),
|
|
cell = _.find(this.cells, function (c) {
|
|
return ((c.x == x) && (c.y == y));
|
|
});
|
|
if (cell) {
|
|
cell.value = value;
|
|
} else {
|
|
this.cells.push({x: x, y: y, value: value});
|
|
}
|
|
},
|
|
|
|
get_value: function (id1, id2) {
|
|
var x = Math.min(id1, id2),
|
|
y = Math.max(id1, id2),
|
|
cell = _.find(this.cells, function (c) {
|
|
return ((c.x == x) && (c.y == y));
|
|
});
|
|
return (cell === undefined) ? undefined : cell.value;
|
|
},
|
|
|
|
get_header: function (id) {
|
|
return _.find(this.rows.headers.concat(this.cols.headers), function (header) {
|
|
return header.id == id;
|
|
});
|
|
},
|
|
|
|
fold: function (header) {
|
|
var list = [];
|
|
function tree_traversal(tree) {
|
|
list.push(tree);
|
|
_.each(tree.children, tree_traversal);
|
|
}
|
|
tree_traversal(header);
|
|
var ids_to_remove = _.map(_.rest(list), function (h) { return h.id;});
|
|
|
|
header.root.headers = _.difference(header.root.headers, _.rest(list));
|
|
header.is_expanded = false;
|
|
var fold_lvls = _.map(header.root.headers, function(g) {return g.path.length;});
|
|
var new_groupby_length = _.max(fold_lvls);
|
|
header.root.groupby.splice(new_groupby_length);
|
|
header.children = [];
|
|
this.cells = _.reject(this.cells, function (cell) {
|
|
return (_.contains(ids_to_remove, cell.x) || _.contains(ids_to_remove, cell.y));
|
|
});
|
|
},
|
|
|
|
expand: function (row_id, field_id) {
|
|
var self = this,
|
|
row = this.get_header(row_id);
|
|
|
|
if (row.path.length == row.root.groupby.length) {
|
|
row.root.groupby.push(field_id);
|
|
}
|
|
|
|
var otherDim = (row.root === this.cols) ? this.rows : this.cols;
|
|
return query_groups_data(this.model, this.visible_fields(), row.domain, otherDim.groupby, field_id)
|
|
.then(function (groups) {
|
|
_.each(groups.reverse(), function (group) {
|
|
var new_row_id = self.make_header(group, row);
|
|
_.each(group, function (data) {
|
|
var col = _.find(otherDim.headers, function (c) {
|
|
if (row.root === self.cols) {
|
|
return _.isEqual(data.path.slice(1), c.path);
|
|
} else {
|
|
return _.isEqual(_.rest(data.path), c.path);
|
|
}
|
|
});
|
|
if (col) {
|
|
self.set_value(new_row_id, col.id, data.attributes.aggregates[self.measure]);
|
|
}
|
|
});
|
|
});
|
|
row.is_expanded = true;
|
|
});
|
|
},
|
|
|
|
make_header: function (groups, parent) {
|
|
var name = groups[0].attributes.value[1],
|
|
new_header = {
|
|
id: this.generate_id(),
|
|
path: parent.path.concat(name),
|
|
title: name,
|
|
is_expanded: false,
|
|
parent: parent.id,
|
|
children: [],
|
|
domain: groups[0].model._domain,
|
|
root: parent.root,
|
|
};
|
|
parent.children.splice(0,0, new_header);
|
|
insertAfter(parent.root.headers, parent, new_header);
|
|
return new_header.id;
|
|
},
|
|
|
|
swap_axis: function () {
|
|
var temp = this.rows;
|
|
this.rows = this.cols;
|
|
this.cols = temp;
|
|
|
|
this.rows.main.title = 'Total';
|
|
this.cols.main.title = this.measure_label;
|
|
},
|
|
|
|
fold_rows: function () {
|
|
this.fold(this.rows.main);
|
|
},
|
|
|
|
fold_cols: function () {
|
|
this.fold(this.cols.main);
|
|
},
|
|
|
|
fold_all: function () {
|
|
this.fold_rows();
|
|
this.fold_cols();
|
|
},
|
|
|
|
expand_all: function () {
|
|
var self = this;
|
|
|
|
return this.query_all_values().then(function (result) {
|
|
self.rows.headers = result.row_headers;
|
|
self.cols.headers = result.col_headers;
|
|
self.cells = result.cells;
|
|
self.rows.main = self.rows.headers[0];
|
|
self.cols.main = self.cols.headers[0];
|
|
self.total = self.rows.main.total;
|
|
self.rows.main.title = 'Total';
|
|
self.cols.main.title = self.measure_label;
|
|
_.each(self.rows.headers, function (row) {
|
|
row.root = self.rows;
|
|
row.is_expanded = (row.children.length > 0);
|
|
});
|
|
_.each(self.cols.headers, function (col) {
|
|
col.root = self.cols;
|
|
col.is_expanded = (col.children.length > 0);
|
|
});
|
|
|
|
});
|
|
},
|
|
|
|
update_values: function () {
|
|
var self = this;
|
|
return this.query_all_values().then(function (result) {
|
|
var old_col_headers = self.cols.headers,
|
|
old_row_headers = self.rows.headers;
|
|
|
|
// the next 30 lines replace this.cols.headers with the
|
|
// corresponding headers in result.col_headers
|
|
_.each(old_col_headers, function (header) {
|
|
var corresponding_col = _.find(result.col_headers, function (c) {
|
|
return _.isEqual(c.path, header.path);
|
|
});
|
|
|
|
if (corresponding_col !== undefined) {
|
|
if (header.is_expanded) {
|
|
corresponding_col.is_expanded = true;
|
|
_.each(corresponding_col.children, function (c) {
|
|
c.is_expanded = false;
|
|
});
|
|
} else {
|
|
corresponding_col.is_expanded = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
self.cols.headers = _.filter(result.col_headers, function (header) {
|
|
return (header.is_expanded !== undefined);
|
|
});
|
|
|
|
_.each(self.cols.headers, function (col) {
|
|
if (!col.is_expanded) {
|
|
col.children = [];
|
|
}
|
|
});
|
|
self.cols.main = self.cols.headers[0];
|
|
|
|
// the next 30 lines replace this.rows.headers with the
|
|
// corresponding headers in result.row_headers
|
|
_.each(old_row_headers, function (header) {
|
|
var corresponding_row = _.find(result.row_headers, function (c) {
|
|
return _.isEqual(c.path, header.path);
|
|
});
|
|
|
|
if (corresponding_row !== undefined) {
|
|
if (header.is_expanded) {
|
|
corresponding_row.is_expanded = true;
|
|
_.each(corresponding_row.children, function (c) {
|
|
c.is_expanded = false;
|
|
});
|
|
} else {
|
|
corresponding_row.is_expanded = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
self.rows.headers = _.filter(result.row_headers, function (header) {
|
|
return (header.is_expanded !== undefined);
|
|
});
|
|
|
|
_.each(self.rows.headers, function (col) {
|
|
if (!col.is_expanded) {
|
|
col.children = [];
|
|
}
|
|
});
|
|
self.rows.main = self.rows.headers[0];
|
|
|
|
// now some more tweaks
|
|
self.total = self.rows.main.total;
|
|
_.each(self.rows.headers, function (row) {
|
|
row.root = self.rows;
|
|
});
|
|
_.each(self.cols.headers, function (col) {
|
|
col.root = self.cols;
|
|
});
|
|
self.cells = result.cells;
|
|
self.rows.main.title = 'Total';
|
|
self.cols.main.title = self.measure_label;
|
|
});
|
|
},
|
|
|
|
// this method is a little tricky. In order to obtain all the values
|
|
// required to draw the full table, we have to do at least
|
|
// 2 + min(row.groupby.length, col.groupby.length)
|
|
// calls to readgroup. For example, if row.groupby = [r1, r2, r3] and
|
|
// col.groupby = [c1, c2, c3, c4], then a minimal set of calls is done
|
|
// with the following groupby:
|
|
// []
|
|
// [c1, c2, c3, c4]
|
|
// [r1, c1, c2, c3, c4]
|
|
// [r1, r2, c1, c2, c3, c4]
|
|
// [r1, r2, r3, c1, c2, c3, c4]
|
|
// To simplify the code, we will always do 2 + row.groupby.length calls,
|
|
// unless col.groupby.length = 0, in which case we do 2 calls ([] and
|
|
// row_groupbys), but this can be optimized later.
|
|
query_all_values: function () {
|
|
var self = this,
|
|
cols = this.cols.groupby,
|
|
rows = this.rows.groupby,
|
|
def_array,
|
|
groupbys;
|
|
|
|
if (cols.length > 0) {
|
|
groupbys = _.map(_.range(rows.length + 1), function (i) {
|
|
return rows.slice(0, i).concat(cols);
|
|
});
|
|
groupbys.push([]);
|
|
} else {
|
|
groupbys = [rows, []];
|
|
}
|
|
def_array = _.map(groupbys, function (groupby) {
|
|
return query_groups(self.model, self.visible_fields(), self.domain, groupby);
|
|
});
|
|
|
|
return $.when.apply(null, def_array).then(function () {
|
|
var args = Array.prototype.slice.call(arguments),
|
|
col_data = _.first(args),
|
|
total = _.last(args)[0],
|
|
row_data = _.last(_.initial(args)),
|
|
cell_data = args;
|
|
|
|
return self.format_data(total, col_data, row_data, cell_data);
|
|
});
|
|
|
|
},
|
|
|
|
format_data: function (total, col_data, row_data, cell_data) {
|
|
var self = this,
|
|
dim_row = this.rows.groupby.length,
|
|
dim_col = this.cols.groupby.length,
|
|
col_headers = make_headers(col_data, dim_col),
|
|
row_headers = make_headers(row_data, dim_row),
|
|
cells = [];
|
|
|
|
if (dim_col > 0) {
|
|
_.each(cell_data, function (data, index) {
|
|
make_cells(data, index, []);
|
|
}); // make it more functional?
|
|
} else {
|
|
make_cells(cell_data[0], dim_row, []);
|
|
make_cells(cell_data[1], 0, []);
|
|
}
|
|
|
|
return {col_headers: col_headers,
|
|
row_headers: row_headers,
|
|
cells: cells};
|
|
|
|
function make_headers (data, depth) {
|
|
var main = {
|
|
id: self.generate_id(),
|
|
count: total.attributes.length,
|
|
total: total.attributes.aggregates[self.measure],
|
|
path: [],
|
|
parent: null,
|
|
children: [],
|
|
title: '',
|
|
domain: self.domain,
|
|
};
|
|
|
|
if (depth > 0) {
|
|
main.children = _.map(data, function (data_pt) {
|
|
return make_tree_headers (data_pt, main, depth);
|
|
});
|
|
}
|
|
|
|
var result = [],
|
|
make_list = function (tree) {
|
|
result.push(tree);
|
|
_.each(tree.children, make_list);
|
|
};
|
|
|
|
make_list(main);
|
|
return result;
|
|
}
|
|
|
|
function make_tree_headers (data_pt, parent, max_depth) {
|
|
var node = {
|
|
id: self.generate_id(),
|
|
count: data_pt.attributes.length,
|
|
total: data_pt.attributes.aggregates[self.measure],
|
|
path: parent.path.concat(data_pt.attributes.value[1]),
|
|
title: data_pt.attributes.value[1],
|
|
domain: data_pt.model._domain,
|
|
parent: parent,
|
|
children: [],
|
|
};
|
|
if (node.path.length < max_depth) {
|
|
node.children = _.map(data_pt.subgroups_data, function (child) {
|
|
return make_tree_headers (child, node, max_depth);
|
|
});
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function make_cells (data, index, current_path) {
|
|
_.each(data, function (group) {
|
|
var attr = group.attributes,
|
|
path = current_path,
|
|
value = attr.aggregates[self.measure];
|
|
|
|
if (attr.grouped_on !== undefined) {
|
|
path = path.concat(attr.value[1]);
|
|
}
|
|
var rowpath = path.slice(0, index),
|
|
colpath = path.slice(index);
|
|
|
|
var row = _.find(row_headers, function (header) {
|
|
return _.isEqual(header.path, rowpath);
|
|
});
|
|
var col = _.find(col_headers, function (header) {
|
|
return _.isEqual(header.path, colpath);
|
|
});
|
|
cells.push({x: Math.min(row.id, col.id),
|
|
y: Math.max(row.id, col.id),
|
|
value: value});
|
|
|
|
if (attr.has_children) {
|
|
make_cells (group.subgroups_data, index, path);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
});
|
|
|
|
function insertAfter(array, after, elem) {
|
|
array.splice(array.indexOf(after) + 1, 0, elem);
|
|
}
|
|
|
|
/**
|
|
* Query the server and return a deferred which will return the data
|
|
* with all the groupbys applied (this is done for now, but the goal
|
|
* is to modify read_group in order to allow eager and lazy groupbys
|
|
*/
|
|
function query_groups (model, fields, domain, groupbys) {
|
|
return model.query(fields)
|
|
.filter(domain)
|
|
.group_by(groupbys)
|
|
.then(function (results) {
|
|
var non_empty_results = _.filter(results, function (group) {
|
|
return group.attributes.length > 0;
|
|
});
|
|
if (groupbys.length <= 1) {
|
|
return non_empty_results;
|
|
} else {
|
|
var get_subgroups = $.when.apply(null, _.map(non_empty_results, function (result) {
|
|
var new_domain = result.model._domain;
|
|
var new_groupings = groupbys.slice(1);
|
|
return query_groups(model, fields,new_domain, new_groupings).then(function (subgroups) {
|
|
result.subgroups_data = subgroups;
|
|
});
|
|
}));
|
|
return get_subgroups.then(function () {
|
|
return non_empty_results;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function query_groups_data (model, fields, domain, row_groupbys, col_groupby) {
|
|
return query_groups(model, fields, domain, [col_groupby].concat(row_groupbys)).then(function (groups) {
|
|
return _.map(groups, function (group) {
|
|
return format_group(group, []);
|
|
});
|
|
});
|
|
}
|
|
|
|
function format_group (group, path) {
|
|
group.path = path.concat(group.attributes.value[1]);
|
|
var result = [group];
|
|
_.each(group.subgroups_data, function (subgroup) {
|
|
result = result.concat(format_group (subgroup, group.path));
|
|
});
|
|
return result;
|
|
}
|
|
|
|
})(); |