OCA reporting engine fork for dev and update.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

445 lines
20 KiB

  1. odoo.define('bi_view_editor', function (require) {
  2. "use strict";
  3. var Core = require("web.core");
  4. var FormCommon = require('web.form_common');
  5. var Model = require('web.Model');
  6. var Data = require('web.data');
  7. var Widget = require('web.Widget');
  8. var Dialog = require("web.Dialog");
  9. var _t = Core._t;
  10. var JoinNodePopup = Widget.extend({
  11. template: "JoinNodePopup",
  12. start: function() {
  13. var self = this;
  14. },
  15. display_popup: function(choices, model_data, callback, callback_data) {
  16. var self = this;
  17. this.renderElement();
  18. var joinnodes = this.$el.find('#join-nodes');
  19. joinnodes.empty();
  20. for (var i=0; i<choices.length; i++) {
  21. var description = "";
  22. if (choices[i].join_node !== -1 && choices[i].table_alias !== -1) {
  23. description = _t("Use the field on model") + " <b>" + model_data[choices[i].table_alias].model_name + "</b>";
  24. } else {
  25. var new_str = "";
  26. if (choices[i].join_node !== -1) {
  27. new_str = "<b>" + _t("new") + "</b> ";
  28. }
  29. 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>";
  30. }
  31. joinnodes.append($('<a><input type="radio">' + description+ '</a>')
  32. .data('idx', i)
  33. .wrap('<p></p>')
  34. .parent());
  35. }
  36. var dialog = new Dialog(this, {
  37. dialogClass: 'oe_act_window',
  38. title: _t("Choose join node"),
  39. $content: this.$el,
  40. buttons: [{text: _t("Cancel"),
  41. classes: "btn-default o_form_button_cancel",
  42. close: true
  43. }]
  44. }).open();
  45. joinnodes.find('a').click(function() {
  46. callback(callback_data, choices[$(this).data('idx')]);
  47. dialog.close();
  48. });
  49. this.start();
  50. }
  51. });
  52. var BiViewEditor = FormCommon.AbstractField.extend({
  53. template: "BVEEditor",
  54. activeModelMenus: [],
  55. currentFilter: "",
  56. init: function() {
  57. this._super.apply(this, arguments);
  58. },
  59. start: function() {
  60. this._super();
  61. this.on("change:effective_readonly", this, function() {
  62. this.display_field();
  63. this.render_value();
  64. });
  65. this.display_field();
  66. this.render_value();
  67. },
  68. display_field: function () {
  69. var self = this;
  70. this.$el.find(".body .right").droppable({
  71. accept: "div.class-list div.field",
  72. drop: function (event, ui) {
  73. self.add_field(ui.draggable);
  74. ui.draggable.draggable('option', 'revert', false );
  75. }
  76. });
  77. if (!this.get("effective_readonly")) {
  78. this.$el.find('.search-bar').attr('disabled', false);
  79. this.$el.find('.class-list').css('opacity', '1');
  80. this.$el.find('.class-list .class').css('cursor', 'pointer');
  81. this.$el.find(".body .right").droppable("option", "disabled", false);
  82. this.$el.find('#clear').css('display', 'inline-block').click(function () {
  83. self.set_fields([]);
  84. self.internal_set_value('[]');
  85. });
  86. this.$el.find('.search-bar input').keyup(function(e) {
  87. //Local filter
  88. self.filter($(this).val());
  89. });
  90. } else {
  91. this.$el.find(".body .right").droppable("option", "disabled", true);
  92. this.$el.find('#clear').css('display', 'none');
  93. this.$el.find('.search-bar').attr('disabled', true);
  94. this.$el.find('.class-list').css('opacity', '.35');
  95. this.$el.find('.class-list .class').css('cursor', 'default');
  96. }
  97. },
  98. filter: function(val) {
  99. val = (typeof val !== 'undefined') ? val.toLowerCase() : this.currentFilter;
  100. this.currentFilter = val;
  101. this.$el.find(".class-list .class-container").each(function() {
  102. var modelData = $(this).find(".class").data('model-data');
  103. //TODO: filter on all model fields (name, technical name, etc)
  104. if(typeof modelData === 'undefined' || (modelData.name.toLowerCase().indexOf(val) === -1 && modelData.model.toLowerCase().indexOf(val) === -1))
  105. $(this).hide();
  106. else
  107. $(this).show();
  108. });
  109. },
  110. get_field_icons: function(field) {
  111. var icons = "";
  112. if(field.column)
  113. icons += "<span class='fa fa-columns' title='Column'></span> ";
  114. if(field.row)
  115. icons += "<span class='fa fa-bars' title='Row'></span> ";
  116. if(field.measure)
  117. icons += "<span class='fa fa-bar-chart-o' title='Measure'></span> ";
  118. if(field.list)
  119. icons += "<span class='fa fa-list' title='List'></span> ";
  120. return icons;
  121. },
  122. update_field_view: function(row) {
  123. row.find("td:nth-child(3)").html(this.get_field_icons(row.data('field-data')));
  124. },
  125. render_value: function() {
  126. this.set_fields(JSON.parse(this.get('value')));
  127. },
  128. load_classes: function(scrollTo) {
  129. scrollTo = (typeof scrollTo === 'undefined') ? false : scrollTo;
  130. var self = this;
  131. var model = new Model("ir.model");
  132. if (this.$el.find(".field-list tbody tr").length > 0) {
  133. model.call("get_related_models", [this.get_model_ids()], { context: new Data.CompoundContext() }).then(function(result) {
  134. self.show_classes(result);
  135. });
  136. } else {
  137. model.call("get_models", { context: new Data.CompoundContext() }).then(function(result) {
  138. self.show_classes(result);
  139. });
  140. }
  141. },
  142. show_classes: function (result) {
  143. var self = this;
  144. var model = new Model("ir.model");
  145. self.$el.find(".class-list .class").remove();
  146. self.$el.find(".class-list .field").remove();
  147. var css = this.get('effective_readonly') ? 'cursor: default' : 'cursor: pointer';
  148. function addField() {
  149. if (!self.get("effective_readonly")) {
  150. self.add_field($(this));
  151. }
  152. }
  153. function clickHandler(evt) {
  154. if(self.get("effective_readonly")) return;
  155. var classel = $(this);
  156. if (classel.data('bve-processed')) {
  157. classel.parent().find('.field').remove();
  158. classel.data('bve-processed', false);
  159. var index = self.activeModelMenus.indexOf(classel.data('model-data').id);
  160. if(index !== -1) self.activeModelMenus.splice(index, 1);
  161. } else {
  162. self.activeModelMenus.push(classel.data('model-data').id);
  163. model.call("get_fields", [classel.data('model-data').id], { context: new Data.CompoundContext() }).then(function(result) {
  164. for (var i = 0; i < result.length; i++) {
  165. classel.find("#bve-field-" + result[i].name).remove();
  166. self._render_field(self, i, result, classel, addField)
  167. }
  168. });
  169. $(this).data('bve-processed', true);
  170. }
  171. }
  172. function renderFields(result) {
  173. if (typeof(result[0]) !== 'undefined') {
  174. var item = self.$el.find(".class-list #bve-class-" + result[0].model_id);
  175. for (var o = 0; o < result.length; o++) {
  176. self._render_field(self, o, result, item, addField)
  177. }
  178. item.data('bve-processed', true);
  179. }
  180. }
  181. for (var i = 0; i < result.length; i++) {
  182. var item = $("<div style=\"" + css + "\" class=\"class\" title=\"" + result[i].model + "\" id=\"bve-class-" + result[i].id + "\">" + result[i].name + "</div>")
  183. .data('model-data', result[i])
  184. .click(clickHandler)
  185. .wrap("<div class=\"class-container\"></div>").parent();
  186. self.$el.find(".class-list").append(item);
  187. var index = self.activeModelMenus.indexOf(item.find(".class").data('model-data').id);
  188. if(index !== -1 && !self.get("effective_readonly")) {
  189. model.call("get_fields", [self.activeModelMenus[index]], { context: new Data.CompoundContext() }).then(renderFields);
  190. }
  191. self.filter();
  192. }
  193. },
  194. _render_field(_self, _index, _result, _item, _addField) {
  195. if(_self.$el.find(".field-list tbody [name=label-" + _result[_index].id + "]").length > 0) return;
  196. _item.after($("<div class=\"field\" title=\"" + _result[_index].name + "\" id=\"bve-field-" + _result[_index].name + "\">" + _result[_index].description + "</div>")
  197. .data('field-data', _result[_index])
  198. .click(_addField)
  199. .draggable({
  200. 'revert': 'invalid',
  201. 'scroll': false,
  202. 'helper': 'clone',
  203. 'appendTo': 'body',
  204. 'containment': 'window'
  205. })
  206. );
  207. },
  208. set_checkbox: function(check, identifier, _contextMenu) {
  209. if(check)
  210. _contextMenu.find(identifier).attr('checked', true);
  211. else
  212. _contextMenu.find(identifier).attr('checked', false);
  213. },
  214. _false_if_undefined: function(to_check) {
  215. if (typeof to_check === 'undefined') return false;
  216. return to_check;
  217. },
  218. _true_if_undefined: function(to_check) {
  219. if (typeof to_check === 'undefined') return true;
  220. return to_check;
  221. },
  222. add_field_to_table: function(data, options) {
  223. var self = this;
  224. data.row = self._false_if_undefined(data.row);
  225. data.column = self._false_if_undefined(data.column);
  226. data.measure = self._false_if_undefined(data.measure);
  227. data.list = self._true_if_undefined(data.list);
  228. var n = 1;
  229. var name = data.name;
  230. function checkNameMatches(el) { return el.name === data.name;}
  231. while ($.grep(self.get_fields(), checkNameMatches).length > 0) {
  232. data.name = name + '_' + n;
  233. n += 1;
  234. }
  235. var classes = "";
  236. if (typeof data.join_node !== 'undefined') {
  237. classes = "join-node displaynone";
  238. }
  239. var delete_button = "";
  240. var disabled = " disabled=\"disabled\" ";
  241. if (!this.get("effective_readonly")) {
  242. delete_button = "<span id=\"delete-" + data.id + "\" class=\"delete-button fa fa-trash-o\"/>";
  243. disabled = "";
  244. }
  245. self.$el.find(".field-list tbody")
  246. .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>")
  247. .data('field-data', data)
  248. .contextmenu(function(e) {
  249. e.preventDefault();
  250. if (self.get("effective_readonly")) return;
  251. var target = $(e.currentTarget);
  252. var currentFieldData = target.data('field-data');
  253. var contextMenu = self.$el.find(".context-menu");
  254. contextMenu.css("left", e.pageX + "px");
  255. contextMenu.css("top", e.pageY + "px");
  256. contextMenu.mouseleave(function() {
  257. contextMenu.hide();
  258. });
  259. contextMenu.find("li").hover(function() {
  260. $(this).find("ul").css("color", "#000");
  261. $(this).find("ul").show();
  262. }, function() {
  263. $(this).find("ul").hide();
  264. });
  265. //Set checkboxes
  266. self.set_checkbox(currentFieldData.column, '#column-checkbox', contextMenu);
  267. self.set_checkbox(currentFieldData.row, '#row-checkbox', contextMenu);
  268. self.set_checkbox(currentFieldData.measure, '#measure-checkbox', contextMenu);
  269. self.set_checkbox(currentFieldData.list, '#list-checkbox', contextMenu);
  270. var to_disable = false;
  271. if(currentFieldData.type === "float" || currentFieldData.type === "integer" || currentFieldData.type === "monetary") to_disable = true;
  272. var identifiers = [['#column-checkbox', 'column', to_disable], ['#row-checkbox', 'row', to_disable], ['#measure-checkbox', 'measure', !to_disable], ['#list-checkbox', 'list', false]];
  273. identifiers.forEach(function (element) {
  274. contextMenu.find(element[0]).attr('disabled', element[2]);
  275. });
  276. //Add change events
  277. identifiers.forEach(function (element) {
  278. contextMenu.find(element[0]).unbind("change");
  279. contextMenu.find(element[0]).change(function() {
  280. currentFieldData[element[1]] = $(this).is(":checked");
  281. target.data('field-data', currentFieldData);
  282. self.update_field_view(target);
  283. self.internal_set_value(JSON.stringify(self.get_fields()));
  284. });
  285. });
  286. contextMenu.show();
  287. $(document).mouseup(function (e) {
  288. var container = $(".context-menu");
  289. // if the target of the click isn't the container nor a descendant of the container
  290. if (!container.is(e.target) && container.has(e.target).length === 0)
  291. {
  292. container.hide();
  293. }
  294. });
  295. })
  296. );
  297. self.$el.find('.delete-button').unbind("click");
  298. self.$el.find('.delete-button').click(function() {
  299. $(this).closest('tr').remove();
  300. self.clean_join_nodes();
  301. self.internal_set_value(JSON.stringify(self.get_fields()));
  302. self.load_classes();
  303. self.$el.find(".field-list .delete-button").hide();
  304. self.$el.find(".field-list .delete-button:last").show();
  305. return false;
  306. });
  307. self.$el.find(".field-list .delete-button").hide();
  308. self.$el.find(".field-list .delete-button:last").show();
  309. },
  310. clean_join_nodes: function () {
  311. var aliases = $.makeArray(this.$el.find(".field-list tbody tr").map(function (idx, el) {
  312. var d = $(this).data('field-data');
  313. return d.table_alias;
  314. }));
  315. this.$el.find(".field-list tbody tr").each(function (idx, el) {
  316. var d = $(this).data('field-data');
  317. if (typeof d.join_node !== 'undefined' && aliases.indexOf(d.join_node) === -1) {
  318. $(this).remove();
  319. }
  320. });
  321. },
  322. get_model_ids: function () {
  323. var model_ids = {};
  324. this.$el.find(".field-list tbody tr").each(function (idx, el) {
  325. var d = $(this).data('field-data');
  326. model_ids[d.table_alias] = d.model_id;
  327. });
  328. return model_ids;
  329. },
  330. get_model_data: function () {
  331. var model_data = {};
  332. this.$el.find(".field-list tbody tr").each(function (idx, el) {
  333. var d = $(this).data('field-data');
  334. model_data[d.table_alias] = {model_id: d.model_id, model_name: d.model_name};
  335. });
  336. return model_data;
  337. },
  338. get_table_alias: function(field) {
  339. if (typeof field.table_alias !== 'undefined') {
  340. return field.table_alias;
  341. } else {
  342. var model_ids = this.get_model_ids();
  343. var n = 0;
  344. while (typeof model_ids["t" + n] !== 'undefined') n++;
  345. return "t" + n;
  346. }
  347. },
  348. add_field_and_join_node: function(field, join_node) {
  349. var self = this;
  350. var go_to_else = true;
  351. if (join_node.join_node === -1 || join_node.table_alias === -1){
  352. go_to_else = false;
  353. field.table_alias = self.get_table_alias(field);
  354. if (join_node.join_node === -1) join_node.join_node = field.table_alias;
  355. else join_node.table_alias = field.table_alias;
  356. self.add_field_to_table(join_node);
  357. }
  358. else field.table_alias = join_node.table_alias;
  359. self.add_field_to_table(field);
  360. self.internal_set_value(JSON.stringify(self.get_fields()));
  361. self.load_classes(field);
  362. },
  363. add_field: function(field) {
  364. var self = this;
  365. // Quick fix for double click
  366. if(self._adding) {
  367. return;
  368. }
  369. self._adding = true;
  370. setTimeout(function() {
  371. self._adding = false;
  372. }, 1000);
  373. // End quick fix
  374. var data = field.data('field-data');
  375. var model = new Model("ir.model");
  376. var model_ids = this.get_model_ids();
  377. var field_data = this.get_fields();
  378. model.call('get_join_nodes', [field_data, data], {context: new Data.CompoundContext()}).then(function(result) {
  379. if (result.length === 1) {
  380. self.add_field_and_join_node(data, result[0]);
  381. self.internal_set_value(JSON.stringify(self.get_fields()));
  382. } else if (result.length > 1) {
  383. var pop = new JoinNodePopup(self);
  384. pop.display_popup(result, self.get_model_data(), self.add_field_and_join_node.bind(self), data);
  385. } else {
  386. // first field and table only.
  387. var table_alias = self.get_table_alias(data);
  388. data.table_alias = table_alias;
  389. self.add_field_to_table(data);
  390. self.internal_set_value(JSON.stringify(self.get_fields()));
  391. self.load_classes(data);
  392. }
  393. });
  394. },
  395. get_fields: function() {
  396. return $.makeArray(this.$el.find(".field-list tbody tr").map(function (idx, el) {
  397. var d = $(this).data('field-data');
  398. d.description = $("input[name='label-" + d.id + "']").val();
  399. return d;
  400. }));
  401. },
  402. set_fields: function(values) {
  403. this.activeModelMenus = [];
  404. if (!values) {
  405. values = [];
  406. }
  407. this.$el.find('.field-list tbody tr').remove();
  408. for(var i = 0; i < values.length; i++) {
  409. this.add_field_to_table(values[i]);
  410. }
  411. this.load_classes();
  412. }
  413. });
  414. Core.form_widget_registry.add('BVEEditor', BiViewEditor);
  415. });