[MIG] bi_view_editor: Migration to 11.0

This commit is contained in:
tarteo
2018-03-22 11:16:32 +01:00
committed by Andrea
parent 4411ce66f5
commit f6e4bfa01b
90 changed files with 4373 additions and 9398 deletions

View File

@@ -83,6 +83,14 @@
cursor: pointer;
}
.oe_form_field_bi_editor .body .left .class-list.readonly {
opacity: .35;
}
.oe_form_field_bi_editor .body .left .class-list .class.readonly {
cursor: default;
}
.oe_form_field_bi_editor .body .left .class-list .class:hover {
background-color: #7C7BAD;
color: #FFF;
@@ -130,8 +138,13 @@
background-color: #DDD;
}
.oe_form_field_bi_editor .body .right .field-list tbody tr.join-node {
background-color: #D2D2FF;
text-align: center;
border-top: 1px solid #DDDDDD;
}
.oe_form_field_bi_editor .context-menu, .oe_form_field_bi_editor .context-menu ul {
display: none;
z-index: 1000;
position: fixed;
background-color: #fff;
@@ -140,15 +153,26 @@
list-style-type: none;
padding: 0;
width: 175px;
}
.oe_form_field_bi_editor .context-menu .checkbox {
margin-top: 0px;
margin-bottom: 0px;
}
.oe_form_field_bi_editor .context-menu .checkbox label {
display: block;
}
.oe_form_field_bi_editor .context-menu li {
padding: 5px;
height: 28px;
cursor: pointer;
}
.oe_form_field_bi_editor .clear-btn {
display: inline-block;
}
.oe_form_field_bi_editor .context-menu li:hover {
background-color: #7C7BAD;
color: #FFF;
@@ -166,11 +190,10 @@
margin-top: -24px;
}
.clear {
clear: both;
}
.displaynone {
display: none;
}
.oe_bi_view_editor_join_node_dialog li {
cursor: pointer;
}

View File

@@ -0,0 +1,305 @@
/* Copyright 2015-2018 Onestein (<http://www.onestein.eu>)
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
odoo.define('bi_view_editor.FieldList', function(require) {
"use strict";
var core = require('web.core');
var qweb = core.qweb;
var Widget = require('web.Widget');
var FieldListContextMenu = Widget.extend({
start: function() {
var res = this._super.apply(this, arguments);
this.$el.mouseleave(function() {
$(this).addClass('hidden');
});
return res;
},
open: function(x, y) {
this.$el.css({
'left': x + 'px',
'top': y + 'px'
});
this.$el.removeClass('hidden');
return _.extend({}, window.Backbone.Events);
}
});
var FieldListFieldContextMenu = FieldListContextMenu.extend({
template: 'bi_view_editor.FieldList.FieldContextMenu',
open: function(x, y, field) {
this.$el.find('.checkbox-column').prop('checked', field.column);
this.$el.find('.checkbox-row').prop('checked', field.row);
this.$el.find('.checkbox-measure').prop('checked', field.measure);
this.$el.find('.checkbox-list').prop('checked', field.list);
var measureable =
field.type === "float" ||
field.type === "integer" ||
field.type === "monetary"
;
this.$el.find('.checkbox-column').attr('disabled', measureable);
this.$el.find('.checkbox-row').attr('disabled', measureable);
this.$el.find('.checkbox-measure').attr('disabled', !measureable);
this.$el.find('.checkbox-list').attr('disabled', false);
var events = this._super(x, y, field);
this.$el.find('input').unbind('change');
this.$el.find('input').change(function() {
var $checkbox = $(this);
var property = $checkbox.attr('data-for');
field[property] = $checkbox.is(':checked');
events.trigger('change', field);
});
return events;
}
});
var FieldListJoinContextMenu = FieldListContextMenu.extend({
template: 'bi_view_editor.FieldList.JoinContextMenu',
open: function(x, y, node) {
this.$el.find('.checkbox-join-left').prop('checked', node.join_left);
var events = this._super(x, y, node);
this.$el.find('input').unbind('change');
this.$el.find('input').change(function() {
var $checkbox = $(this);
var property = $checkbox.attr('data-for');
node[property] = $checkbox.is(':checked');
events.trigger('change', node);
});
return events;
}
});
var FieldList = Widget.extend({
template: 'bi_view_editor.FieldList',
events: {
'click .delete-button': 'removeClicked',
'keyup input[name="description"]': 'keyupDescription'
},
start: function() {
var res = this._super.apply(this, arguments);
this.contextmenu = new FieldListFieldContextMenu(this);
this.contextmenu.appendTo(this.$el);
this.contextmenu_join = new FieldListJoinContextMenu(this);
this.contextmenu_join.appendTo(this.$el);
this.$table = this.$el.find('tbody');
this.mode = null;
return res;
},
setMode: function(mode) {
if(mode === 'readonly') {
this.$el.find('input[type="text"]').attr('disabled', true);
this.$el.find(".delete-button:last").addClass('hidden');
} else {
this.$el.find('input[type="text"]').removeAttr('disabled');
this.$el.find(".delete-button:last").removeClass('hidden');
}
this.mode = mode;
},
get: function() {
return $.makeArray(this.$el.find("tbody tr").map(function () {
var field = $(this).data('field');
field.description = $(this).find('input[name="description"]').val();
return field;
}));
},
getModelIds: function () {
var model_ids = {};
this.$el.find("tbody tr").each(function () {
var data = $(this).data('field');
model_ids[data.table_alias] = data.model_id;
});
return model_ids;
},
getModelData: function () {
var model_data = {};
this.$el.find("tbody tr").each(function () {
var data = $(this).data('field');
model_data[data.table_alias] = {
model_id: data.model_id,
model_name: data.model_name
};
});
return model_data;
},
add: function(field) {
var self = this;
field.row = typeof field.row === 'undefined' ? false : field.row;
field.column = typeof field.column === 'undefined' ? false : field.column;
field.measure = typeof field.measure === 'undefined' ? false : field.measure;
field.list = typeof field.list === 'undefined' ? false : field.list;
field._id = typeof field._id === 'undefined' ? _.uniqueId('node_') : field._id;
if(field.join_node) {
field.join_left = typeof field.join_left === 'undefined' ? false : field.join_left;
}
var i = 0;
var name = field.name;
while (this.get().filter(function(item) {
return item.name === field.name;
}).length > 0) {
field.name = name + '_' + i;
i++;
}
// Render table row
var $html = $(qweb.render(field.join_node ? 'bi_view_editor.JoinListItem' : 'bi_view_editor.FieldListItem', {
'field': field
})).data('field', field).contextmenu(function(e) {
var $item = $(this);
if(self.mode === 'readonly') {
return;
}
e.preventDefault();
self.openContextMenu($item, e.pageX, e.pageY);
});
this.$el.find('tbody').append($html);
this.$el.find(".delete-button").addClass('hidden');
this.$el.find(".delete-button:last").removeClass('hidden');
this.order();
},
remove: function(id) {
var $item = this.$el.find('tr[data-id="' + id + '"]');
$item.remove();
this.cleanJoinNodes();
this.$el.find(".delete-button").addClass('hidden');
this.$el.find(".delete-button:last").removeClass('hidden');
this.trigger('removed', id);
},
set: function(fields) {
var set_fields = fields;
if (!set_fields) {
set_fields = [];
}
this.$el.find('tbody tr').remove();
for(var i = 0; i < set_fields.length; i++) {
this.add(set_fields[i]);
}
this.$el.find(".delete-button").addClass('hidden');
this.$el.find(".delete-button:last").removeClass('hidden');
},
openContextMenu: function($item, x, y) {
var field = $item.data('field');
var contextmenu = field.join_node ? this.contextmenu_join : this.contextmenu;
// Temporary disable contextmenu for join node (until left join is implemented)
if (field.join_node) {
return;
}
contextmenu.open(x - 20, y - 20, $item.data('field')).on('change', function(f) {
$item.data('field', f);
this.refreshItem($item);
this.trigger('updated');
}.bind(this));
},
refreshItem: function($item) {
var data = $item.data('field');
var $attributes = $item.find('span[data-for], img[data-for]');
$.each($attributes, function() {
var $attribute = $(this);
var value = data[$attribute.attr('data-for')];
if(value) {
$attribute.removeClass('hidden');
} else {
$attribute.addClass('hidden');
}
});
},
removeClicked: function(e) {
var $button = $(e.currentTarget);
var id = $button.attr('data-id');
this.remove(id);
},
keyupDescription: function() {
this.trigger('updated');
},
cleanJoinNodes: function() {
var aliases = $.makeArray(this.$el.find("tbody tr").map(function () {
var data = $(this).data('field');
return data.table_alias.localeCompare(data.join_node) > 0 ? data.join_node : data.table_alias;
}));
this.$el.find("tbody tr").each(function () {
var data = $(this).data('field');
if (typeof data.join_node === 'undefined') {
return;
}
var no_alias = data.table_alias.localeCompare(data.join_node) > 0 &&
aliases.indexOf(data.table_alias) === -1;
if (no_alias ||
aliases.indexOf(data.join_node) === -1) {
$(this).remove();
}
});
},
getOrder: function() {
var items = this.get();
var ordered = items.sort(function(a, b) {
var res = a.table_alias.localeCompare(b.table_alias);
if (res === 0) {
var both_join_node = a.join_node && b.join_node;
var both_not_join_node = !a.join_node && !b.join_node;
if(both_join_node || both_not_join_node) {
return 0;
} else if(!a.join_node && b.join_node) {
if(b.table_alias.localeCompare(b.join_node) > 0) {
return 1;
}
return -1;
} else if(a.join_node && !b.join_node) {
if(a.table_alias.localeCompare(a.join_node) > 0) {
return -1;
}
return 1;
}
}
return res;
});
var res = [];
_.each(ordered, function(item) {
var already_exists = _.findIndex(res, function(f) {
return f._id === item._id;
}) !== -1;
if(already_exists) {
return;
}
res.push(item);
if(item.join_node) {
var join_node_fields = _.filter(ordered, function(f) {
return f.table_alias === item.join_node && !f.join_node;
});
res = _.union(res, join_node_fields);
}
});
return res;
},
order: function() {
var order = this.getOrder();
var $rows = this.$el.find("tbody tr");
$rows.sort(function(a, b) {
var a_index = _.findIndex(order, function(item) {
return item._id === $(a).data('field')._id;
});
var b_index = _.findIndex(order, function(item) {
return item._id === $(b).data('field')._id;
});
return a_index - b_index;
}).appendTo(this.$el.find("tbody"));
}
});
return {
'FieldList': FieldList,
'FieldListContextMenu': FieldListContextMenu,
'FieldListFieldContextMenu': FieldListFieldContextMenu,
'FieldListJoinContextMenu': FieldListJoinContextMenu
};
});

View File

@@ -0,0 +1,52 @@
/* Copyright 2015-2018 Onestein (<http://www.onestein.eu>)
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
odoo.define('bi_view_editor.JoinNodeDialog', function (require) {
"use strict";
var Dialog = require("web.Dialog");
var core = require('web.core');
var qweb = core.qweb;
var _t = core._t;
var JoinNodeDialog = Dialog.extend({
xmlDependencies: Dialog.prototype.xmlDependencies.concat([
'/bi_view_editor/static/src/xml/bi_view_editor.xml'
]),
events: {
"click li": "choiceClicked",
},
init: function(parent, options, choices, model_data) {
this.choices = choices;
// Prepare data for view
for (var i = 0; i < choices.length; i++) {
if (choices[i].join_node !== -1 && choices[i].table_alias !== -1) {
choices[i].model_name = model_data[choices[i].table_alias].model_name;
}
choices[i].index = i;
}
var defaults = _.defaults(options || {}, {
title: _t("Join..."),
dialogClass: 'oe_act_window',
$content: qweb.render('bi_view_editor.JoinNodeDialog', {
'choices': choices
}),
buttons: [{
text: _t("Cancel"),
classes: "btn-default o_form_button_cancel",
close: true
}],
});
this._super(parent, defaults);
},
choiceClicked: function(e) {
this.trigger('chosen', {
choice: this.choices[$(e.currentTarget).attr('data-index')]
});
this.close();
}
});
return JoinNodeDialog;
});

View File

@@ -0,0 +1,171 @@
/* Copyright 2015-2018 Onestein (<http://www.onestein.eu>)
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
odoo.define('bi_view_editor.ModelList', function (require) {
"use strict";
var Widget = require('web.Widget');
var core = require('web.core');
var session = require('web.session');
var qweb = core.qweb;
var ModelList = Widget.extend({
template: 'bi_view_editor.ModelList',
events: {
'keyup .search-bar > input': 'filterChanged'
},
init: function(parent) {
var res = this._super(parent);
this.active_models = [];
this.cache_fields = {};
this.current_filter = '';
this.mode = null;
return res;
},
setMode: function(mode) {
if(mode === 'readonly') {
this.$el.find('.search-bar').attr('disabled', true);
this.$el.find('.class-list, .class').addClass('readonly');
} else {
this.$el.find('.search-bar').attr('disabled', false);
this.$el.find('.class-list, .class').removeClass('readonly');
}
this.mode = mode;
},
isActive: function(id) {
return this.active_models.indexOf(id) !== -1;
},
removeAsActive: function(id) {
var i = this.active_models.indexOf(id);
this.active_models.splice(i, 1);
},
addAsActive: function(id) {
this.active_models.push(id);
},
loadModels: function(model_ids) {
if (model_ids) {
return this._rpc({
model: 'ir.model',
method: 'get_related_models',
args: [model_ids],
context: {
lang: session.user_context.lang
}
});
}
return this._rpc({
model: 'ir.model',
method: 'get_models',
context: {
lang: session.user_context.lang
}
});
},
loadFields: function(model_id) {
if (!(model_id in this.cache_fields)) {
var deferred = this._rpc({
model: 'ir.model',
method: 'get_fields',
args: [model_id],
context: {
lang: session.user_context.lang
}
});
this.cache_fields[model_id] = deferred;
}
return this.cache_fields[model_id];
},
populateModels: function (models) {
var self = this;
this.$el.find(".class-list").html('');
_.each(models, function(model) {
var $html = $(qweb.render('bi_view_editor.ModelListItem', {
'id': model.id,
'model': model.model,
'name': model.name
}));
$html.find('.class').data('model', model).click(function() {
self.modelClicked($(this));
});
self.$el.find(".class-list").append($html);
if (self.isActive(model.id)) {
self.loadFields(model.id).done(function(fields) {
self.populateFields(fields, model.id);
});
}
});
},
populateFields: function(fields, model_id) {
var self = this;
if(!model_id && fields.length === 0) {
return;
}
var data_model_id = model_id;
if(!data_model_id) {
data_model_id = fields[0].model_id;
}
var $model_item = this.$el.find(".class[data-id='" + data_model_id + "']");
_.each(fields, function(field) {
var $field = $(qweb.render('bi_view_editor.ModelListFieldItem', {
name: field.name,
description: field.description
})).data('field', field).click(function() {
self.fieldClicked($(this));
}).draggable({
'revert': 'invalid',
'scroll': false,
'helper': 'clone',
'appendTo': 'body',
'containment': 'window'
});
$model_item.after($field);
});
},
modelClicked: function($el) {
if (this.mode === 'readonly') {
return;
}
var model = $el.data('model');
$el.parent().find('.field').remove();
if(this.isActive(model.id)) {
this.removeAsActive(model.id);
} else {
this.addAsActive(model.id);
this.loadFields(model.id).done(function(fields) {
this.populateFields(fields, model.id);
}.bind(this));
}
},
fieldClicked: function($el) {
if (this.mode === 'readonly') {
return;
}
this.trigger('field_clicked', $el.data('field'));
},
filterChanged: function(e) {
var $input = $(e.target);
this.filter($input.val());
},
filter: function(value) {
this.active_models = [];
this.$el.find('.field').remove();
var val = typeof value === 'undefined' ? this.current_filter : value.toLowerCase();
this.$el.find(".class").each(function() {
var data = $(this).data('model');
if (data.name.toLowerCase().indexOf(val) === -1 &&
data.model.toLowerCase().indexOf(val) === -1) {
$(this).addClass('hidden');
} else {
$(this).removeClass('hidden');
}
});
this.current_filter = val;
}
});
return ModelList;
});

View File

@@ -0,0 +1,143 @@
/* Copyright 2015-2018 Onestein (<http://www.onestein.eu>)
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
odoo.define('bi_view_editor', function (require) {
"use strict";
var JoinNodeDialog = require('bi_view_editor.JoinNodeDialog');
var ModelList = require('bi_view_editor.ModelList');
var FieldList = require('bi_view_editor.FieldList').FieldList;
var AbstractField = require('web.AbstractField');
var Data = require('web.data');
var field_registry = require('web.field_registry');
var BiViewEditor = AbstractField.extend({
template: "bi_view_editor.Frame",
events: {
"click .clear-btn": "clear"
},
start: function() {
var self = this;
var res = this._super.apply(this, arguments);
// Init ModelList
this.model_list = new ModelList(this);
this.model_list.appendTo(this.$(".body > .left"));
this.model_list.on('field_clicked', this, function(field) {
self.addField(_.extend({}, field));
});
// Init FieldList
this.field_list = new FieldList(this);
this.field_list.appendTo(this.$(".body > .right"));
this.field_list.on('removed', this, this.fieldListRemoved);
this.field_list.on('updated', this, this.fieldListChanged);
this.$el.find(".body > .right").droppable({
accept: "div.class-list div.field",
drop: function (event, ui) {
self.addField(_.extend({}, ui.draggable.data('field')));
ui.draggable.draggable('option', 'revert', false);
}
});
this.on("change:effective_readonly", this, function() {
this.updateMode();
});
this.renderValue();
this.loadAndPopulateModelList();
this.updateMode();
return res;
},
clear: function() {
if (this.mode !== 'readonly') {
this.field_list.set([]);
this.loadAndPopulateModelList();
this._setValue(JSON.stringify(this.field_list.get()));
}
},
fieldListChanged: function() {
this._setValue(JSON.stringify(this.field_list.get()));
},
fieldListRemoved: function() {
this.loadAndPopulateModelList();
this._setValue(JSON.stringify(this.field_list.get()));
},
renderValue: function() {
this.field_list.set(JSON.parse(this.value));
},
updateMode: function () {
if (this.mode === 'readonly') {
this.$el.find('.clear-btn').addClass('hidden');
this.$el.find(".body .right").droppable("option", "disabled", true);
} else {
this.$el.find('.clear-btn').removeClass('hidden');
this.$el.find('.body .right').droppable('option', 'disabled', false);
}
this.field_list.setMode(this.mode);
this.model_list.setMode(this.mode);
},
loadAndPopulateModelList: function() {
var model_ids = null;
if (this.field_list.get().length > 0) {
model_ids = this.field_list.getModelIds();
}
this.model_list.loadModels(model_ids).done(function(models) {
this.model_list.populateModels(models);
}.bind(this));
},
getTableAlias: function(field) {
if (typeof field.table_alias === 'undefined') {
var model_ids = this.field_list.getModelIds();
var n = 0;
while (typeof model_ids["t" + n] !== 'undefined') {
n++;
}
return "t" + n;
}
return field.table_alias;
},
addFieldAndJoinNode: function(field, join_node) {
if (join_node.join_node === -1 || join_node.table_alias === -1) {
field.table_alias = this.getTableAlias(field);
if (join_node.join_node === -1) {
join_node.join_node = field.table_alias;
} else {
join_node.table_alias = field.table_alias;
}
this.field_list.add(join_node);
} else {
field.table_alias = join_node.table_alias;
}
this.field_list.add(field);
this.loadAndPopulateModelList();
this._setValue(JSON.stringify(this.field_list.get()));
},
addField: function(field) {
var data = _.extend({}, field);
var model = new Data.DataSet(this, "ir.model");
var field_data = this.field_list.get();
model.call('get_join_nodes', [field_data, data]).then(function(result) {
if (result.length === 1) {
this.addFieldAndJoinNode(data, result[0]);
} else if (result.length > 1) {
var dialog = new JoinNodeDialog(this, {}, result, this.field_list.getModelData());
dialog.open().on('chosen', this, function(e) {
this.addFieldAndJoinNode(data, e.choice);
});
} else {
var table_alias = this.getTableAlias(data);
data.table_alias = table_alias;
this.field_list.add(data);
this.loadAndPopulateModelList();
this._setValue(JSON.stringify(this.field_list.get()));
}
}.bind(this));
}
});
field_registry.add('BVEEditor', BiViewEditor);
});

View File

@@ -1,445 +0,0 @@
odoo.define('bi_view_editor', function (require) {
"use strict";
var Core = require("web.core");
var FormCommon = require('web.form_common');
var Model = require('web.Model');
var Data = require('web.data');
var Widget = require('web.Widget');
var Dialog = require("web.Dialog");
var _t = Core._t;
var JoinNodePopup = Widget.extend({
template: "JoinNodePopup",
start: function() {
var self = this;
},
display_popup: function(choices, model_data, callback, callback_data) {
var self = this;
this.renderElement();
var joinnodes = this.$el.find('#join-nodes');
joinnodes.empty();
for (var i=0; i<choices.length; i++) {
var description = "";
if (choices[i].join_node !== -1 && choices[i].table_alias !== -1) {
description = _t("Use the field on model") + " <b>" + model_data[choices[i].table_alias].model_name + "</b>";
} else {
var new_str = "";
if (choices[i].join_node !== -1) {
new_str = "<b>" + _t("new") + "</b> ";
}
description = _t("<b>Join</b> using the field") + " <u><b>" + choices[i].description + "</b></u> " + _t("on ") + new_str + _t("model") +" <b>" + choices[i].model_name + "</b>";
}
joinnodes.append($('<a><input type="radio">' + description+ '</a>')
.data('idx', i)
.wrap('<p></p>')
.parent());
}
var dialog = new Dialog(this, {
dialogClass: 'oe_act_window',
title: _t("Choose join node"),
$content: this.$el,
buttons: [{text: _t("Cancel"),
classes: "btn-default o_form_button_cancel",
close: true
}]
}).open();
joinnodes.find('a').click(function() {
callback(callback_data, choices[$(this).data('idx')]);
dialog.close();
});
this.start();
}
});
var BiViewEditor = FormCommon.AbstractField.extend({
template: "BVEEditor",
activeModelMenus: [],
currentFilter: "",
init: function() {
this._super.apply(this, arguments);
},
start: function() {
this._super();
this.on("change:effective_readonly", this, function() {
this.display_field();
this.render_value();
});
this.display_field();
this.render_value();
},
display_field: function () {
var self = this;
this.$el.find(".body .right").droppable({
accept: "div.class-list div.field",
drop: function (event, ui) {
self.add_field(ui.draggable);
ui.draggable.draggable('option', 'revert', false );
}
});
if (!this.get("effective_readonly")) {
this.$el.find('.search-bar').attr('disabled', false);
this.$el.find('.class-list').css('opacity', '1');
this.$el.find('.class-list .class').css('cursor', 'pointer');
this.$el.find(".body .right").droppable("option", "disabled", false);
this.$el.find('#clear').css('display', 'inline-block').click(function () {
self.set_fields([]);
self.internal_set_value('[]');
});
this.$el.find('.search-bar input').keyup(function(e) {
//Local filter
self.filter($(this).val());
});
} else {
this.$el.find(".body .right").droppable("option", "disabled", true);
this.$el.find('#clear').css('display', 'none');
this.$el.find('.search-bar').attr('disabled', true);
this.$el.find('.class-list').css('opacity', '.35');
this.$el.find('.class-list .class').css('cursor', 'default');
}
},
filter: function(val) {
val = (typeof val !== 'undefined') ? val.toLowerCase() : this.currentFilter;
this.currentFilter = val;
this.$el.find(".class-list .class-container").each(function() {
var modelData = $(this).find(".class").data('model-data');
//TODO: filter on all model fields (name, technical name, etc)
if(typeof modelData === 'undefined' || (modelData.name.toLowerCase().indexOf(val) === -1 && modelData.model.toLowerCase().indexOf(val) === -1))
$(this).hide();
else
$(this).show();
});
},
get_field_icons: function(field) {
var icons = "";
if(field.column)
icons += "<span class='fa fa-columns' title='Column'></span> ";
if(field.row)
icons += "<span class='fa fa-bars' title='Row'></span> ";
if(field.measure)
icons += "<span class='fa fa-bar-chart-o' title='Measure'></span> ";
if(field.list)
icons += "<span class='fa fa-list' title='List'></span> ";
return icons;
},
update_field_view: function(row) {
row.find("td:nth-child(3)").html(this.get_field_icons(row.data('field-data')));
},
render_value: function() {
this.set_fields(JSON.parse(this.get('value')));
},
load_classes: function(scrollTo) {
scrollTo = (typeof scrollTo === 'undefined') ? false : scrollTo;
var self = this;
var model = new Model("ir.model");
if (this.$el.find(".field-list tbody tr").length > 0) {
model.call("get_related_models", [this.get_model_ids()], { context: new Data.CompoundContext() }).then(function(result) {
self.show_classes(result);
});
} else {
model.call("get_models", { context: new Data.CompoundContext() }).then(function(result) {
self.show_classes(result);
});
}
},
show_classes: function (result) {
var self = this;
var model = new Model("ir.model");
self.$el.find(".class-list .class").remove();
self.$el.find(".class-list .field").remove();
var css = this.get('effective_readonly') ? 'cursor: default' : 'cursor: pointer';
function addField() {
if (!self.get("effective_readonly")) {
self.add_field($(this));
}
}
function clickHandler(evt) {
if(self.get("effective_readonly")) return;
var classel = $(this);
if (classel.data('bve-processed')) {
classel.parent().find('.field').remove();
classel.data('bve-processed', false);
var index = self.activeModelMenus.indexOf(classel.data('model-data').id);
if(index !== -1) self.activeModelMenus.splice(index, 1);
} else {
self.activeModelMenus.push(classel.data('model-data').id);
model.call("get_fields", [classel.data('model-data').id], { context: new Data.CompoundContext() }).then(function(result) {
for (var i = 0; i < result.length; i++) {
classel.find("#bve-field-" + result[i].name).remove();
self._render_field(self, i, result, classel, addField)
}
});
$(this).data('bve-processed', true);
}
}
function renderFields(result) {
if (typeof(result[0]) !== 'undefined') {
var item = self.$el.find(".class-list #bve-class-" + result[0].model_id);
for (var o = 0; o < result.length; o++) {
self._render_field(self, o, result, item, addField)
}
item.data('bve-processed', true);
}
}
for (var i = 0; i < result.length; i++) {
var item = $("<div style=\"" + css + "\" class=\"class\" title=\"" + result[i].model + "\" id=\"bve-class-" + result[i].id + "\">" + result[i].name + "</div>")
.data('model-data', result[i])
.click(clickHandler)
.wrap("<div class=\"class-container\"></div>").parent();
self.$el.find(".class-list").append(item);
var index = self.activeModelMenus.indexOf(item.find(".class").data('model-data').id);
if(index !== -1 && !self.get("effective_readonly")) {
model.call("get_fields", [self.activeModelMenus[index]], { context: new Data.CompoundContext() }).then(renderFields);
}
self.filter();
}
},
_render_field(_self, _index, _result, _item, _addField) {
if(_self.$el.find(".field-list tbody [name=label-" + _result[_index].id + "]").length > 0) return;
_item.after($("<div class=\"field\" title=\"" + _result[_index].name + "\" id=\"bve-field-" + _result[_index].name + "\">" + _result[_index].description + "</div>")
.data('field-data', _result[_index])
.click(_addField)
.draggable({
'revert': 'invalid',
'scroll': false,
'helper': 'clone',
'appendTo': 'body',
'containment': 'window'
})
);
},
set_checkbox: function(check, identifier, _contextMenu) {
if(check)
_contextMenu.find(identifier).attr('checked', true);
else
_contextMenu.find(identifier).attr('checked', false);
},
_false_if_undefined: function(to_check) {
if (typeof to_check === 'undefined') return false;
return to_check;
},
_true_if_undefined: function(to_check) {
if (typeof to_check === 'undefined') return true;
return to_check;
},
add_field_to_table: function(data, options) {
var self = this;
data.row = self._false_if_undefined(data.row);
data.column = self._false_if_undefined(data.column);
data.measure = self._false_if_undefined(data.measure);
data.list = self._true_if_undefined(data.list);
var n = 1;
var name = data.name;
function checkNameMatches(el) { return el.name === data.name;}
while ($.grep(self.get_fields(), checkNameMatches).length > 0) {
data.name = name + '_' + n;
n += 1;
}
var classes = "";
if (typeof data.join_node !== 'undefined') {
classes = "join-node displaynone";
}
var delete_button = "";
var disabled = " disabled=\"disabled\" ";
if (!this.get("effective_readonly")) {
delete_button = "<span id=\"delete-" + data.id + "\" class=\"delete-button fa fa-trash-o\"/>";
disabled = "";
}
self.$el.find(".field-list tbody")
.append($("<tr class=\"" + classes + "\"><td><input " + disabled + "title=\"" + data.name + " (" + data.model + ")\" type=\"text\" name=\"label-" + data.id + "\" value=\"" + data.description + "\"/></td><td>" + data.model_name + "</td><td>" + self.get_field_icons(data) + "</td><td>" + delete_button + "</td></tr>")
.data('field-data', data)
.contextmenu(function(e) {
e.preventDefault();
if (self.get("effective_readonly")) return;
var target = $(e.currentTarget);
var currentFieldData = target.data('field-data');
var contextMenu = self.$el.find(".context-menu");
contextMenu.css("left", e.pageX + "px");
contextMenu.css("top", e.pageY + "px");
contextMenu.mouseleave(function() {
contextMenu.hide();
});
contextMenu.find("li").hover(function() {
$(this).find("ul").css("color", "#000");
$(this).find("ul").show();
}, function() {
$(this).find("ul").hide();
});
//Set checkboxes
self.set_checkbox(currentFieldData.column, '#column-checkbox', contextMenu);
self.set_checkbox(currentFieldData.row, '#row-checkbox', contextMenu);
self.set_checkbox(currentFieldData.measure, '#measure-checkbox', contextMenu);
self.set_checkbox(currentFieldData.list, '#list-checkbox', contextMenu);
var to_disable = false;
if(currentFieldData.type === "float" || currentFieldData.type === "integer" || currentFieldData.type === "monetary") to_disable = true;
var identifiers = [['#column-checkbox', 'column', to_disable], ['#row-checkbox', 'row', to_disable], ['#measure-checkbox', 'measure', !to_disable], ['#list-checkbox', 'list', false]];
identifiers.forEach(function (element) {
contextMenu.find(element[0]).attr('disabled', element[2]);
});
//Add change events
identifiers.forEach(function (element) {
contextMenu.find(element[0]).unbind("change");
contextMenu.find(element[0]).change(function() {
currentFieldData[element[1]] = $(this).is(":checked");
target.data('field-data', currentFieldData);
self.update_field_view(target);
self.internal_set_value(JSON.stringify(self.get_fields()));
});
});
contextMenu.show();
$(document).mouseup(function (e) {
var container = $(".context-menu");
// if the target of the click isn't the container nor a descendant of the container
if (!container.is(e.target) && container.has(e.target).length === 0)
{
container.hide();
}
});
})
);
self.$el.find('.delete-button').unbind("click");
self.$el.find('.delete-button').click(function() {
$(this).closest('tr').remove();
self.clean_join_nodes();
self.internal_set_value(JSON.stringify(self.get_fields()));
self.load_classes();
self.$el.find(".field-list .delete-button").hide();
self.$el.find(".field-list .delete-button:last").show();
return false;
});
self.$el.find(".field-list .delete-button").hide();
self.$el.find(".field-list .delete-button:last").show();
},
clean_join_nodes: function () {
var aliases = $.makeArray(this.$el.find(".field-list tbody tr").map(function (idx, el) {
var d = $(this).data('field-data');
return d.table_alias;
}));
this.$el.find(".field-list tbody tr").each(function (idx, el) {
var d = $(this).data('field-data');
if (typeof d.join_node !== 'undefined' && aliases.indexOf(d.join_node) === -1) {
$(this).remove();
}
});
},
get_model_ids: function () {
var model_ids = {};
this.$el.find(".field-list tbody tr").each(function (idx, el) {
var d = $(this).data('field-data');
model_ids[d.table_alias] = d.model_id;
});
return model_ids;
},
get_model_data: function () {
var model_data = {};
this.$el.find(".field-list tbody tr").each(function (idx, el) {
var d = $(this).data('field-data');
model_data[d.table_alias] = {model_id: d.model_id, model_name: d.model_name};
});
return model_data;
},
get_table_alias: function(field) {
if (typeof field.table_alias !== 'undefined') {
return field.table_alias;
} else {
var model_ids = this.get_model_ids();
var n = 0;
while (typeof model_ids["t" + n] !== 'undefined') n++;
return "t" + n;
}
},
add_field_and_join_node: function(field, join_node) {
var self = this;
var go_to_else = true;
if (join_node.join_node === -1 || join_node.table_alias === -1){
go_to_else = false;
field.table_alias = self.get_table_alias(field);
if (join_node.join_node === -1) join_node.join_node = field.table_alias;
else join_node.table_alias = field.table_alias;
self.add_field_to_table(join_node);
}
else field.table_alias = join_node.table_alias;
self.add_field_to_table(field);
self.internal_set_value(JSON.stringify(self.get_fields()));
self.load_classes(field);
},
add_field: function(field) {
var self = this;
// Quick fix for double click
if(self._adding) {
return;
}
self._adding = true;
setTimeout(function() {
self._adding = false;
}, 1000);
// End quick fix
var data = field.data('field-data');
var model = new Model("ir.model");
var model_ids = this.get_model_ids();
var field_data = this.get_fields();
model.call('get_join_nodes', [field_data, data], {context: new Data.CompoundContext()}).then(function(result) {
if (result.length === 1) {
self.add_field_and_join_node(data, result[0]);
self.internal_set_value(JSON.stringify(self.get_fields()));
} else if (result.length > 1) {
var pop = new JoinNodePopup(self);
pop.display_popup(result, self.get_model_data(), self.add_field_and_join_node.bind(self), data);
} else {
// first field and table only.
var table_alias = self.get_table_alias(data);
data.table_alias = table_alias;
self.add_field_to_table(data);
self.internal_set_value(JSON.stringify(self.get_fields()));
self.load_classes(data);
}
});
},
get_fields: function() {
return $.makeArray(this.$el.find(".field-list tbody tr").map(function (idx, el) {
var d = $(this).data('field-data');
d.description = $("input[name='label-" + d.id + "']").val();
return d;
}));
},
set_fields: function(values) {
this.activeModelMenus = [];
if (!values) {
values = [];
}
this.$el.find('.field-list tbody tr').remove();
for(var i = 0; i < values.length; i++) {
this.add_field_to_table(values[i]);
}
this.load_classes();
}
});
Core.form_widget_registry.add('BVEEditor', BiViewEditor);
});

View File

@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="utf-8"?>
<template>
<t t-name="bi_view_editor.Frame">
<div class="oe_form_field_bi_editor">
<div class="body">
<div class="left">
</div>
<div class="right">
</div>
<div class="clear"></div>
</div>
<div class="footer">
<div class="left"></div>
<div class="right"><button class="clear-btn hidden"><span class="fa fa-eraser"></span> Clear</button></div>
</div>
</div>
</t>
<!-- Join Node Dialog -->
<t t-name="bi_view_editor.JoinNodeDialog">
<div class="oe_bi_view_editor_join_node_dialog">
<ul class="list-group" >
<t t-foreach="choices" t-as="choice">
<t t-if="choice.join_node !== -1 and choice.table_alias !== -1">
<li class="list-group-item list-group-item-action text-primary" t-attf-data-index="#{choice.index}">
<b>Use the existing node</b>
</li>
</t>
<t t-elif="choice.join_node !== -1">
<li class="list-group-item list-group-item-action text-success" t-attf-data-index="#{choice.index}">
Use the field
<b><t t-esc="choice.model_name"/></b>
<i class="fa fa-caret-right"/>
<b><t t-esc="choice.description"/></b>
<span class="badge">new</span>
</li>
</t>
<t t-else="">
<li class="list-group-item list-group-item-action" t-attf-data-index="#{choice.index}">
Use the field
<b><t t-esc="choice.model_name"/></b>
<i class="fa fa-caret-right"/>
<b><t t-esc="choice.description"/></b>
</li>
</t>
</t>
</ul>
</div>
</t>
<!-- ModelList -->
<t t-name="bi_view_editor.ModelList">
<div>
<div class="search-bar">
<span class="fa fa-search"></span>
<input type="text" class="search-bar" />
</div>
<div class="class-list">
</div>
</div>
</t>
<!-- ModelListItem -->
<t t-name="bi_view_editor.ModelListItem">
<div class="class-container">
<div class="class" t-attf-title="#{model}" t-attf-data-id="#{id}"><t t-esc="name"/></div>
</div>
</t>
<!-- ModelListFieldItem-->
<t t-name="bi_view_editor.ModelListFieldItem">
<div class="field" t-attf-title="#{name}" t-attf-data-id="#{name}"><t t-esc="description"/></div>
</t>
<!-- FieldList -->
<t t-name="bi_view_editor.FieldList">
<div>
<table class="field-list">
<thead>
<tr>
<th>Name</th>
<th>Model</th>
<th>Options</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</t>
<!-- FieldContextMenu -->
<t t-name="bi_view_editor.FieldList.FieldContextMenu">
<ul class="context-menu hidden">
<li>
<div class="checkbox">
<label>
<input type="checkbox" data-for="column" class="checkbox-column"/> Column
</label>
</div>
</li>
<li>
<div class="checkbox">
<label>
<input type="checkbox" data-for="row" class="checkbox-row"/> Row
</label>
</div>
</li>
<li>
<div class="checkbox">
<label>
<input type="checkbox" data-for="measure" class="checkbox-measure"/> Measure
</label>
</div>
</li>
<li>
<div class="checkbox">
<label>
<input type="checkbox" data-for="list" class="checkbox-list"/> List
</label>
</div>
</li>
</ul>
</t>
<!-- JoinContextMenu -->
<t t-name="bi_view_editor.FieldList.JoinContextMenu">
<ul class="context-menu hidden">
<li>
<div class="checkbox">
<label>
<input type="checkbox" data-for="join_left" class="checkbox-join-left"/> Join Left
</label>
</div>
</li>
</ul>
</t>
<!-- FieldListItem -->
<t t-name="bi_view_editor.FieldListItem">
<tr t-attf-data-id="#{field._id}" class="field-node">
<td>
<input t-attf-data-id="#{field._id}" t-attf-title="#{field.model_name} (#{field.model})" class="form-control input-sm" type="text" name="description" t-attf-value="#{field.description}"/>
</td>
<td>
<t t-esc="field.model_name" />
</td>
<td>
<span data-for="column" t-attf-class="#{field.column and 'fa fa-columns' or 'fa fa-columns hidden'}" title='Column'></span>
<span data-for="row" t-attf-class="#{field.row and 'fa fa-bars' or 'fa fa-bars hidden'}" title='Row'></span>
<span data-for="measure" t-attf-class="#{field.measure and 'fa fa-bar-chart-o' or 'fa fa-bar-chart-o hidden'}" title='Measure'></span>
<span data-for="list" t-attf-class="#{field.list and 'fa fa-list' or 'fa fa-list hidden'}" title='List'></span>
</td>
<td>
<span t-attf-data-id="#{field._id}" class="delete-button fa fa-trash-o"/>
</td>
</tr>
</t>
<t t-name="bi_view_editor.JoinListItem">
<tr t-attf-data-id="#{field._id}" class="join-node">
<td colspan="4">
<input class="hidden" type="text" name="description" t-attf-value="#{field.description}"/>
<t t-if="field.join_node > field.table_alias">
<b><t t-esc="field.model_name" /></b>
<i class="fa fa-caret-right"/>
<small><t t-esc="field.description" /></small>
</t>
<t t-else="">
<small><t t-esc="field.description" /></small>
<i class="fa fa-caret-left"/>
<b><t t-esc="field.model_name" /></b>
</t>
<span t-attf-class="#{!field.join_left and 'hidden' or ''}" data-for="join_left"><i>(join left)</i></span>
</td>
</tr>
</t>
</template>