Files
odoo_source/addons/web_graph/static/src/js/pivot.js
T

507 lines
14 KiB
JavaScript

/* jshint undef: false */
(function () {
'use strict';
openerp.web_graph.PivotTable = openerp.web.Class.extend(openerp.EventDispatcherMixin, {
// PivotTable require a 'update_data' after init to be ready
// to do: add an option to enable/disable update_data at the end of init
init: function (model, domain) {
openerp.EventDispatcherMixin.init.call(this);
this.rows = { groupby: [], main: null, headers: null };
this.cols = { groupby: [], main: null, headers: null };
this.cells = [];
this.domain = domain;
this.measure = null;
this.data_loader = new openerp.web_graph.DataLoader(model);
this.no_data = true;
},
visible_fields: function () {
var result = this.rows.groupby.concat(this.cols.groupby);
if (this.measure) {
result = result.concat(this.measure);
}
return result;
},
config: function (options) {
var changed = false;
var groupby_changed = false;
var default_options = {
update:true,
domain:this.domain,
col_groupby: this.cols.groupby,
row_groupby: this.rows.groupby,
measure: this.measure,
silent:false
};
var options = _.extend(default_options, options);
if (!_.isEqual(options.domain, this.domain)) {
this.domain = options.domain;
changed = true;
}
if (options.measure !== this.measure) {
this.measure = options.measure;
changed = true;
}
if (!_.isEqual(options.col_groupby, this.cols.groupby)) {
this.cols.groupby = options.col_groupby;
changed = true;
this.cols.headers = null;
groupby_changed = true;
}
if (!_.isEqual(options.row_groupby, this.rows.groupby)) {
this.rows.groupby = options.row_groupby;
this.rows.headers = null;
changed = true;
groupby_changed = true;
}
if (!options.silent && groupby_changed) { this.trigger('groupby_changed'); }
if (options.update && changed) { this.update_data(); }
},
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, default_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));
});
return (cell === undefined) ? default_value : cell.value;
},
is_row: function (id) {
return !!_.find(this.rows.headers, function (header) {
return header.id == id;
});
},
is_col: function (id) {
return !!_.find(this.cols.headers, function (header) {
return header.id == id;
});
},
get_header: function (id) {
return _.find(this.rows.headers.concat(this.cols.headers), function (header) {
return header.id == id;
});
},
// return all columns with a path length of 'depth'
get_columns_depth: function (depth) {
return _.filter(this.cols.headers, function (hdr) {
return hdr.path.length === depth;
});
},
// return all rows with a path length of 'depth'
get_rows_depth: function (depth) {
return _.filter(this.rows.headers, function (hdr) {
return hdr.path.length === depth;
});
},
// return all non expanded rows
get_rows_leaves: function () {
return _.filter(this.rows.headers, function (hdr) {
return !hdr.is_expanded;
});
},
// return all non expanded cols
get_cols_leaves: function () {
return _.filter(this.cols.headers, function (hdr) {
return !hdr.is_expanded;
});
},
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.children = [];
this.cells = _.reject(this.cells, function (cell) {
return (_.contains(ids_to_remove, cell.x) || _.contains(ids_to_remove, cell.y));
});
if (new_groupby_length < header.root.groupby.length) {
header.root.groupby.splice(new_groupby_length);
this.trigger('groupby_changed');
}
this.trigger('redraw_required');
},
expand: function (header_id, field_id) {
var self = this,
header = this.get_header(header_id);
if (header.path.length == header.root.groupby.length) {
header.root.groupby.push(field_id);
this.trigger('groupby_changed');
}
var otherDim = (header.root === this.cols) ? this.rows : this.cols;
return this.data_loader.get_groups(this.visible_fields(), header.domain, otherDim.groupby, {first_groupby:field_id, add_path:true})
.then(function (groups) {
_.each(groups.reverse(), function (group) {
var new_header_id = self.make_header(group, header);
_.each(group, function (data) {
var other = _.find(otherDim.headers, function (h) {
if (header.root === self.cols) {
return _.isEqual(data.path.slice(1), h.path);
} else {
return _.isEqual(_.rest(data.path), h.path);
}
});
if (other) {
if (self.measure) {
self.set_value(new_header_id, other.id, data.attributes.aggregates[self.measure]);
} else {
self.set_value(new_header_id, other.id, data.attributes.length);
}
}
});
});
header.is_expanded = true;
self.trigger('redraw_required');
});
},
make_header: function (groups, parent) {
var name = groups[0].attributes.value,
new_header = {
id: _.uniqueId(),
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);
parent.root.headers.splice(parent.root.headers.indexOf(parent) + 1, 0, new_header);
return new_header.id;
},
swap_axis: function () {
var temp = this.rows;
this.rows = this.cols;
this.cols = temp;
this.trigger('groupby_changed');
this.trigger('redraw_required');
},
get_total: function (header) {
if (header) {
var main = (header.root === this.rows) ? this.cols.main : this.rows.main;
return this.get_value(header.id, main.id);
} else {
return this.rows.main.total;
}
},
update_data: function () {
var self = this,
options = {
col_groupby: this.cols.groupby,
row_groupby: this.rows.groupby,
measure: this.measure,
domain: this.domain,
};
return this.data_loader.load_data(options).then (function (result) {
if (result) {
self.no_data = false;
if (self.cols.headers) {
self.update_headers(self.cols, result.col_headers);
} else {
self.expand_headers(self.cols, result.col_headers);
}
if (self.rows.headers) {
self.update_headers(self.rows, result.row_headers);
} else {
self.expand_headers(self.rows, result.row_headers);
}
self.cells = result.cells;
} else {
self.no_data = true;
}
self.trigger('redraw_required');
});
},
expand_headers: function (root, new_headers) {
root.headers = new_headers;
root.main = new_headers[0];
_.each(root.headers, function (header) {
header.root = root;
header.is_expanded = (header.children.length > 0);
});
},
update_headers: function (root, new_headers) {
_.each(root.headers, function (header) {
var corresponding_header = _.find(new_headers, function (h) {
return _.isEqual(h.path, header.path);
});
if (corresponding_header && (header.is_expanded)) {
corresponding_header.is_expanded = true;
_.each(corresponding_header.children, function (c) {
c.is_expanded = false;
});
}
if (corresponding_header && (!header.is_expanded)) {
corresponding_header.is_expanded = false;
}
});
var updated_headers = _.filter(new_headers, function (header) {
return (header.is_expanded !== undefined);
});
_.each(updated_headers, function (hdr) {
if (!hdr.is_expanded) {
hdr.children = [];
}
hdr.root = root;
});
root.headers = updated_headers;
root.main = root.headers[0];
},
});
openerp.web_graph.DataLoader = openerp.web.Class.extend({
init: function (model) {
this.model = model;
},
get_groups: function (fields, domain, groupbys, options) {
var self = this,
options = (options) ? options : {},
groupings = (options.first_groupby) ? [options.first_groupby].concat(groupbys) : groupbys;
return this.query_db(fields, domain, groupings).then(function (groups) {
return _.map(groups, function (group) {
return (options.add_path) ?self.add_path(group, []) : group;
});
});
},
query_db: function (fields, domain, groupbys) {
var self = this;
return this.model.query(fields)
.filter(domain)
.group_by(groupbys)
.then(function (results) {
var non_empty_results = _.filter(results, function (group) {
return group.attributes.length > 0;
});
_.each(non_empty_results, self.sanitize_value);
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 self.query_db(fields,new_domain, new_groupings).then(function (subgroups) {
result.subgroups_data = subgroups;
});
}));
return get_subgroups.then(function () {
return non_empty_results;
});
}
});
},
sanitize_value: function (group) {
var value = group.attributes.value;
if (value === false) {
group.attributes.value = 'undefined';
} else if (value instanceof Array) {
group.attributes.value = value[1];
} else {
group.attributes.value = value;
}
},
add_path: function (group, current_path) {
var self = this;
group.path = current_path.concat(group.attributes.value);
var result = [group];
_.each(group.subgroups_data, function (subgroup) {
result = result.concat(self.add_path(subgroup, group.path));
});
return result;
},
// 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.
load_data: function (options) {
var self = this,
cols = options.col_groupby,
rows = options.row_groupby,
visible_fields = rows.concat(cols);
if (options.measure) { visible_fields = visible_fields.concat(options.measure); }
var groupbys = _.map(_.range(cols.length + 1), function (i) {
return cols.slice(0, i).concat(rows);
});
groupbys.push([]);
var def_array = _.map(groupbys, function (groupby) {
return self.get_groups(visible_fields, options.domain, groupby);
});
return $.when.apply(null, def_array).then(function () {
var data = Array.prototype.slice.call(arguments),
row_data = data[0],
col_data = (cols.length !== 0) ? data[data.length - 2] : [],
total = data[data.length - 1][0];
return (total === undefined) ? undefined
: self.format_data(total, col_data, row_data, data, options);
});
},
get_value: function (data, measure) {
var attr = data.attributes;
return (measure) ? attr.aggregates[measure] : attr.length;
},
format_data: function (total, col_data, row_data, cell_data, options) {
var self = this,
dim_row = options.row_groupby.length,
dim_col = options.col_groupby.length,
measure = options.measure,
col_headers = make_headers(col_data, dim_col),
row_headers = make_headers(row_data, dim_row),
cells = [];
_.each(cell_data, function (data, index) {
make_cells(data, index, []);
}); // make it more functional?
return {col_headers: col_headers,
row_headers: row_headers,
cells: cells};
function make_headers (data, depth) {
var main = {
id: _.uniqueId(),
path: [],
parent: null,
children: [],
title: '',
domain: options.domain,
total: self.get_value(total, measure),
};
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: _.uniqueId(),
path: parent.path.concat(data_pt.attributes.value),
title: data_pt.attributes.value,
domain: data_pt.model._domain,
parent: parent,
children: [],
total : self.get_value(data_pt, measure),
};
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,
group_val = (attr.value instanceof Array) ? attr.value[1] : attr.value,
path = current_path,
value = (measure) ? attr.aggregates[measure] : attr.length;
group_val = (group_val === false) ? undefined : group_val;
if (attr.grouped_on !== undefined) {
path = path.concat((attr.value === false) ? 'undefined' : group_val);
}
var colpath = path.slice(0, index),
rowpath = 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);
}
});
}
},
});
})();