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.
 
 
 
 
 
 

774 rivejä
23 KiB

  1. // Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. //
  3. // MIT License (MIT)
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a
  6. // copy of this software and associated documentation files (the "Software"),
  7. // to deal in the Software without restriction, including without limitation
  8. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. // and/or sell copies of the Software, and to permit persons to whom the
  10. // Software is furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. //
  22. wn.provide("wn.report_dump");
  23. $.extend(wn.report_dump, {
  24. data: {},
  25. with_data: function(doctypes, callback, progress_bar) {
  26. var missing = [];
  27. $.each(doctypes, function(i, v) {
  28. if(!wn.report_dump.data[v]) missing.push(v);
  29. })
  30. if(missing.length) {
  31. wn.call({
  32. method: "webnotes.widgets.report_dump.get_data",
  33. args: {
  34. doctypes: doctypes,
  35. missing: missing
  36. },
  37. callback: function(r) {
  38. // creating map of data from a list
  39. $.each(r.message, function(doctype, doctype_data) {
  40. var data = [];
  41. $.each(doctype_data.data, function(i, d) {
  42. var row = {};
  43. $.each(doctype_data.columns, function(idx, col) {
  44. row[col] = d[idx];
  45. });
  46. row.id = row.name || doctype + "-" + i;
  47. row.doctype = doctype;
  48. data.push(row);
  49. });
  50. wn.report_dump.data[doctype] = data;
  51. });
  52. // reverse map names
  53. $.each(r.message, function(doctype, doctype_data) {
  54. if(doctype_data.links) {
  55. $.each(wn.report_dump.data[doctype], function(row_idx, row) {
  56. $.each(doctype_data.links, function(link_key, link) {
  57. if(wn.report_dump.data[link[0]][row[link_key]]) {
  58. row[link_key] = wn.report_dump.data[link[0]][row[link_key]][link[1]];
  59. } else {
  60. row[link_key] = null;
  61. }
  62. })
  63. })
  64. }
  65. });
  66. callback();
  67. },
  68. progress_bar: progress_bar
  69. })
  70. } else {
  71. callback();
  72. }
  73. }
  74. });
  75. wn.provide("wn.views");
  76. wn.views.GridReport = Class.extend({
  77. init: function(opts) {
  78. this.filter_inputs = {};
  79. this.preset_checks = [];
  80. this.tree_grid = {show: false};
  81. $.extend(this, opts);
  82. this.wrapper = $('<div>').appendTo(this.parent);
  83. if(this.filters) {
  84. this.make_filters();
  85. }
  86. this.make_waiting();
  87. this.import_slickgrid();
  88. var me = this;
  89. this.get_data();
  90. },
  91. bind_show: function() {
  92. // bind show event to reset cur_report_grid
  93. // and refresh filters from url
  94. // this must be called after init
  95. // because "wn.container.page" will only be set
  96. // once "load" event is over.
  97. var me = this;
  98. $(this.page).bind('show', function() {
  99. // reapply filters on show
  100. wn.cur_grid_report = me;
  101. me.apply_filters_from_route();
  102. me.refresh();
  103. });
  104. },
  105. get_data: function() {
  106. var me = this;
  107. wn.report_dump.with_data(this.doctypes, function() {
  108. // setup filters
  109. me.setup_filters();
  110. me.init_filter_values();
  111. me.refresh();
  112. }, this.wrapper.find(".progress .bar"));
  113. },
  114. setup_filters: function() {
  115. var me = this;
  116. $.each(me.filter_inputs, function(i, v) {
  117. var opts = v.get(0).opts;
  118. if (opts.fieldtype == "Select" && inList(me.doctypes, opts.link)) {
  119. $(v).add_options($.map(wn.report_dump.data[opts.link], function(d) {
  120. return d.name;
  121. }));
  122. }
  123. });
  124. // refresh
  125. this.filter_inputs.refresh && this.filter_inputs.refresh.click(function() {
  126. me.set_route();
  127. });
  128. // reset filters
  129. this.filter_inputs.reset_filters && this.filter_inputs.reset_filters.click(function() {
  130. me.init_filter_values();
  131. me.set_route();
  132. });
  133. this.filter_inputs.range && this.filter_inputs.range.change(function() {
  134. me.set_route();
  135. });
  136. },
  137. init_filter_values: function() {
  138. var me = this;
  139. $.each(this.filter_inputs, function(key, filter) {
  140. var opts = filter.get(0).opts;
  141. if(sys_defaults[key]) {
  142. filter.val(sys_defaults[key]);
  143. } else if(opts.fieldtype=='Select') {
  144. filter.get(0).selectedIndex = 0;
  145. } else if(opts.fieldtype=='Data') {
  146. filter.val("");
  147. }
  148. })
  149. if(this.filter_inputs.from_date)
  150. this.filter_inputs.from_date.val(dateutil.str_to_user(sys_defaults.year_start_date));
  151. if(this.filter_inputs.to_date)
  152. this.filter_inputs.to_date.val(dateutil.str_to_user(sys_defaults.year_end_date));
  153. },
  154. make_filters: function() {
  155. var me = this;
  156. $.each(this.filters, function(i, v) {
  157. v.fieldname = v.fieldname || v.label.replace(/ /g, '_').toLowerCase();
  158. var input = null;
  159. if(v.fieldtype=='Select') {
  160. input = me.appframe.add_select(v.label, v.options || [v.default_value]);
  161. } else if(v.fieldtype=='Button') {
  162. input = me.appframe.add_button(v.label);
  163. if(v.icon) {
  164. $('<i class="icon '+ v.icon +'"></i>').prependTo(input);
  165. }
  166. } else if(v.fieldtype=='Date') {
  167. input = me.appframe.add_date(v.label);
  168. } else if(v.fieldtype=='Label') {
  169. input = me.appframe.add_label(v.label);
  170. } else if(v.fieldtype=='Data') {
  171. input = me.appframe.add_data(v.label);
  172. }
  173. if(input) {
  174. input && (input.get(0).opts = v);
  175. if(v.cssClass) {
  176. input.addClass(v.cssClass);
  177. }
  178. input.keypress(function(e) {
  179. if(e.which==13) {
  180. me.set_route();
  181. }
  182. })
  183. }
  184. me.filter_inputs[v.fieldname] = input;
  185. });
  186. },
  187. make_waiting: function() {
  188. this.waiting = wn.messages.waiting(this.wrapper, "Loading Report...", '10');
  189. },
  190. load_filter_values: function() {
  191. var me = this;
  192. $.each(this.filter_inputs, function(i, f) {
  193. var opts = f.get(0).opts;
  194. if(opts.fieldtype!='Button') {
  195. me[opts.fieldname] = f.val();
  196. if(opts.fieldtype=="Date") {
  197. me[opts.fieldname] = dateutil.user_to_str(me[opts.fieldname]);
  198. } else if (opts.fieldtype == "Select") {
  199. me[opts.fieldname+'_default'] = opts.default_value;
  200. }
  201. }
  202. });
  203. if(this.filter_inputs.from_date && this.filter_inputs.to_date && (this.to_date < this.from_date)) {
  204. msgprint("From Date must be before To Date");
  205. return;
  206. }
  207. },
  208. make_name_map: function(data, key) {
  209. var map = {};
  210. key = key || "name";
  211. $.each(data, function(i, v) {
  212. map[v[key]] = v;
  213. })
  214. return map;
  215. },
  216. reset_item_values: function(item) {
  217. var me = this;
  218. $.each(this.columns, function(i, col) {
  219. if (col.formatter==me.currency_formatter) {
  220. item[col.id] = 0;
  221. }
  222. });
  223. },
  224. import_slickgrid: function() {
  225. wn.require('lib/js/lib/slickgrid/slick.grid.css');
  226. wn.require('lib/js/lib/slickgrid/slick-default-theme.css');
  227. wn.require('lib/js/lib/slickgrid/jquery.event.drag.min.js');
  228. wn.require('lib/js/lib/slickgrid/slick.core.js');
  229. wn.require('lib/js/lib/slickgrid/slick.grid.js');
  230. wn.require('lib/js/lib/slickgrid/slick.dataview.js');
  231. wn.dom.set_style('.slick-cell { font-size: 12px; }');
  232. if(this.tree_grid.show) wn.require("app/js/tree_grid.css");
  233. },
  234. refresh: function() {
  235. this.waiting.toggle(false);
  236. if(!this.grid_wrapper)
  237. this.make();
  238. this.show_zero = $('.show-zero input:checked').length;
  239. this.load_filter_values();
  240. this.setup_columns();
  241. this.setup_dataview_columns();
  242. this.apply_link_formatters();
  243. this.prepare_data();
  244. this.prepare_data_view();
  245. // plot might need prepared data
  246. this.wrapper.find(".processing").toggle(true);
  247. this.wrapper.find(".processing").delay(2000).fadeOut(300);
  248. this.render();
  249. this.render_plot && this.render_plot();
  250. },
  251. setup_dataview_columns: function() {
  252. this.dataview_columns = $.map(this.columns, function(col) {
  253. return !col.hidden ? col : null;
  254. });
  255. },
  256. make: function() {
  257. var me = this;
  258. // plot wrapper
  259. this.plot_area = $('<div class="plot" style="margin-bottom: 15px; display: none; \
  260. height: 300px; width: 100%;"></div>').appendTo(this.wrapper);
  261. // print / export
  262. $('<div style="text-align: right;"> \
  263. <div class="processing" style="background-color: #fec; display: none; float: left; margin: 2px"> \
  264. Updated! </div>\
  265. <a href="#" class="grid-report-print"><i class="icon icon-print"></i> Print</a> \
  266. <span style="color: #aaa; margin: 0px 10px;"> | </span> \
  267. <a href="#" class="grid-report-export"><i class="icon icon-download-alt"></i> Export</a> \
  268. </div>').appendTo(this.wrapper);
  269. this.wrapper.find(".grid-report-export").click(function() { return me.export(); });
  270. this.wrapper.find(".grid-report-print").click(function() { msgprint("Coming Soon"); return false; });
  271. // grid wrapper
  272. this.grid_wrapper = $("<div style='height: 500px; border: 1px solid #aaa; \
  273. background-color: #eee; margin-top: 15px;'>")
  274. .appendTo(this.wrapper);
  275. this.id = wn.dom.set_unique_id(this.grid_wrapper.get(0));
  276. // zero-value check
  277. $('<div style="margin: 10px 0px; text-align: right; display: none" class="show-zero">\
  278. <input type="checkbox"> Show rows with zero values\
  279. </div>').appendTo(this.wrapper);
  280. this.bind_show();
  281. wn.cur_grid_report = this;
  282. this.apply_filters_from_route();
  283. $(this.wrapper).trigger('make');
  284. },
  285. apply_filters_from_route: function() {
  286. var hash = decodeURIComponent(window.location.hash);
  287. var me = this;
  288. if(hash.indexOf('/') != -1) {
  289. $.each(hash.split('/').splice(1).join('/').split('&'), function(i, f) {
  290. var f = f.split("=");
  291. if(me.filter_inputs[f[0]]) {
  292. me.filter_inputs[f[0]].val(decodeURIComponent(f[1]));
  293. } else {
  294. console.log("Invalid filter: " +f[0]);
  295. }
  296. });
  297. } else {
  298. this.init_filter_values();
  299. }
  300. },
  301. set_route: function() {
  302. wn.set_route(wn.container.page.page_name, $.map(this.filter_inputs, function(v) {
  303. var val = v.val();
  304. var opts = v.get(0).opts;
  305. if(val && val != opts.default_value)
  306. return encodeURIComponent(opts.fieldname)
  307. + '=' + encodeURIComponent(val);
  308. }).join('&'))
  309. },
  310. options: {
  311. editable: false,
  312. enableColumnReorder: false
  313. },
  314. render: function() {
  315. // new slick grid
  316. this.grid = new Slick.Grid("#"+this.id, this.dataView, this.dataview_columns, this.options);
  317. var me = this;
  318. // bind events
  319. this.dataView.onRowsChanged.subscribe(function (e, args) {
  320. me.grid.invalidateRows(args.rows);
  321. me.grid.render();
  322. });
  323. this.dataView.onRowCountChanged.subscribe(function (e, args) {
  324. me.grid.updateRowCount();
  325. me.grid.render();
  326. });
  327. this.tree_grid.show && this.add_tree_grid_events();
  328. },
  329. prepare_data_view: function() {
  330. // initialize the model
  331. this.dataView = new Slick.Data.DataView({ inlineFilters: true });
  332. this.dataView.beginUpdate();
  333. this.dataView.setItems(this.data);
  334. if(this.dataview_filter) this.dataView.setFilter(this.dataview_filter);
  335. if(this.tree_grid.show) this.dataView.setFilter(this.tree_dataview_filter);
  336. this.dataView.endUpdate();
  337. },
  338. export: function() {
  339. wn.downloadify(wn.slickgrid_tools.get_view_data(this.columns, this.dataView),
  340. ["Report Manager", "System Manager"]);
  341. return false;
  342. },
  343. apply_filters: function(item) {
  344. // generic filter: apply filter functiions
  345. // from all filter_inputs
  346. var filters = this.filter_inputs;
  347. if(item._show) return true;
  348. for (i in filters) {
  349. if(!this.apply_filter(item, i)) return false;
  350. }
  351. return true;
  352. },
  353. apply_filter: function(item, fieldname) {
  354. var filter = this.filter_inputs[fieldname].get(0);
  355. if(filter.opts.filter) {
  356. if(!filter.opts.filter(this[filter.opts.fieldname], item, filter.opts, this)) {
  357. return false;
  358. }
  359. }
  360. return true;
  361. },
  362. apply_zero_filter: function(val, item, opts, me) {
  363. // show only non-zero values
  364. if(!me.show_zero) {
  365. for(var i=0, j=me.columns.length; i<j; i++) {
  366. var col = me.columns[i];
  367. if(col.formatter==me.currency_formatter && !col.hidden) {
  368. if(flt(item[col.field]) > 0.001 || flt(item[col.field]) < -0.001) {
  369. return true;
  370. }
  371. }
  372. }
  373. return false;
  374. }
  375. return true;
  376. },
  377. show_zero_check: function() {
  378. var me = this;
  379. this.wrapper.bind('make', function() {
  380. me.wrapper.find('.show-zero').toggle(true).find('input').click(function(){
  381. me.refresh();
  382. });
  383. });
  384. },
  385. is_default: function(fieldname) {
  386. return this[fieldname]==this[fieldname + "_default"];
  387. },
  388. date_formatter: function(row, cell, value, columnDef, dataContext) {
  389. return dateutil.str_to_user(value);
  390. },
  391. currency_formatter: function(row, cell, value, columnDef, dataContext) {
  392. return repl('<div style="text-align: right; %(_style)s">%(value)s</div>', {
  393. _style: dataContext._style || "",
  394. value: fmt_money(value)
  395. });
  396. },
  397. text_formatter: function(row, cell, value, columnDef, dataContext) {
  398. return repl('<span style="%(_style)s" title="%(esc_value)s">%(value)s</span>', {
  399. _style: dataContext._style || "",
  400. esc_value: cstr(value).replace(/"/g, '\"'),
  401. value: cstr(value)
  402. });
  403. },
  404. check_formatter: function(row, cell, value, columnDef, dataContext) {
  405. return repl("<input type='checkbox' data-id='%(id)s' \
  406. class='plot-check' %(checked)s>", {
  407. "id": dataContext.id,
  408. "checked": dataContext.checked ? "checked" : ""
  409. })
  410. },
  411. apply_link_formatters: function() {
  412. var me = this;
  413. $.each(this.dataview_columns, function(i, col) {
  414. if(col.link_formatter) {
  415. col.formatter = function(row, cell, value, columnDef, dataContext) {
  416. // added link and open button to links
  417. // link_formatter must have
  418. // filter_input, open_btn (true / false), doctype (will be eval'd)
  419. if(!value) return "";
  420. var me = wn.cur_grid_report;
  421. if(dataContext._show) {
  422. return repl('<span style="%(_style)s">%(value)s</span>', {
  423. _style: dataContext._style || "",
  424. value: value
  425. });
  426. }
  427. // make link to add a filter
  428. var link_formatter = me.dataview_columns[cell].link_formatter;
  429. var html = repl('<a href="#" \
  430. onclick="wn.cur_grid_report.filter_inputs.%(col_name)s.val(\'%(value)s\'); \
  431. wn.cur_grid_report.set_route(); return false;">\
  432. %(value)s</a>', {
  433. value: value,
  434. col_name: link_formatter.filter_input,
  435. page_name: wn.container.page.page_name
  436. })
  437. // make icon to open form
  438. if(link_formatter.open_btn) {
  439. var doctype = link_formatter.doctype ? eval(link_formatter.doctype)
  440. : dataContext.doctype;
  441. html += me.get_link_open_icon(doctype, value);
  442. }
  443. return html;
  444. }
  445. }
  446. })
  447. },
  448. get_link_open_icon: function(doctype, name) {
  449. return repl(' <a href="#Form/%(doctype)s/%(name)s">\
  450. <i class="icon icon-share" style="cursor: pointer;"></i></a>', {
  451. doctype: doctype,
  452. name: encodeURIComponent(name)
  453. });
  454. },
  455. make_date_range_columns: function() {
  456. this.columns = [];
  457. var me = this;
  458. var range = this.filter_inputs.range.val();
  459. this.from_date = dateutil.user_to_str(this.filter_inputs.from_date.val());
  460. this.to_date = dateutil.user_to_str(this.filter_inputs.to_date.val());
  461. var date_diff = dateutil.get_diff(this.to_date, this.from_date);
  462. me.column_map = {};
  463. var add_column = function(date) {
  464. me.columns.push({
  465. id: date,
  466. name: dateutil.str_to_user(date),
  467. field: date,
  468. formatter: me.currency_formatter,
  469. width: 100
  470. });
  471. }
  472. var build_columns = function(condition) {
  473. // add column for each date range
  474. for(var i=0; i < date_diff; i++) {
  475. var date = dateutil.add_days(me.from_date, i);
  476. if(!condition) condition = function() { return true; }
  477. if(condition(date)) add_column(date);
  478. me.last_date = date;
  479. if(me.columns.length) {
  480. me.column_map[date] = me.columns[me.columns.length-1];
  481. }
  482. }
  483. }
  484. // make columns for all date ranges
  485. if(range=='Daily') {
  486. build_columns();
  487. } else if(range=='Weekly') {
  488. build_columns(function(date) {
  489. if(!me.last_date) return true;
  490. return !(dateutil.get_diff(date, me.from_date) % 7)
  491. });
  492. } else if(range=='Monthly') {
  493. build_columns(function(date) {
  494. if(!me.last_date) return true;
  495. return dateutil.str_to_obj(me.last_date).getMonth() != dateutil.str_to_obj(date).getMonth()
  496. });
  497. } else if(range=='Quarterly') {
  498. build_columns(function(date) {
  499. if(!me.last_date) return true;
  500. return dateutil.str_to_obj(date).getDate()==1 && in_list([0,3,6,9], dateutil.str_to_obj(date).getMonth())
  501. });
  502. } else if(range=='Yearly') {
  503. build_columns(function(date) {
  504. if(!me.last_date) return true;
  505. return $.map(wn.report_dump.data['Fiscal Year'], function(v) {
  506. return date==v.year_start_date ? true : null;
  507. }).length;
  508. });
  509. }
  510. // set label as last date of period
  511. $.each(this.columns, function(i, col) {
  512. col.name = me.columns[i+1]
  513. ? dateutil.str_to_user(dateutil.add_days(me.columns[i+1].id, -1))
  514. : dateutil.str_to_user(me.to_date);
  515. });
  516. },
  517. });
  518. wn.views.GridReportWithPlot = wn.views.GridReport.extend({
  519. render_plot: function() {
  520. var plot_data = this.get_plot_data ? this.get_plot_data() : null;
  521. if(!plot_data) {
  522. this.plot_area.toggle(false);
  523. return;
  524. }
  525. wn.require('lib/js/lib/flot/jquery.flot.js');
  526. this.plot = $.plot(this.plot_area.toggle(true), plot_data,
  527. this.get_plot_options());
  528. this.setup_plot_hover();
  529. },
  530. setup_plot_check: function() {
  531. var me = this;
  532. me.wrapper.bind('make', function() {
  533. me.wrapper.on("click", ".plot-check", function() {
  534. var checked = $(this).attr("checked");
  535. me.item_by_name[$(this).attr("data-id")].checked = checked ? true : false;
  536. me.render_plot();
  537. });
  538. });
  539. },
  540. setup_plot_hover: function() {
  541. var me = this;
  542. this.tooltip_id = wn.dom.set_unique_id();
  543. function showTooltip(x, y, contents) {
  544. $('<div id="' + me.tooltip_id + '">' + contents + '</div>').css( {
  545. position: 'absolute',
  546. display: 'none',
  547. top: y + 5,
  548. left: x + 5,
  549. border: '1px solid #fdd',
  550. padding: '2px',
  551. 'background-color': '#fee',
  552. opacity: 0.80
  553. }).appendTo("body").fadeIn(200);
  554. }
  555. this.previousPoint = null;
  556. this.wrapper.find('.plot').bind("plothover", function (event, pos, item) {
  557. if (item) {
  558. if (me.previousPoint != item.dataIndex) {
  559. me.previousPoint = item.dataIndex;
  560. $("#" + me.tooltip_id).remove();
  561. showTooltip(item.pageX, item.pageY,
  562. me.get_tooltip_text(item.series.label, item.datapoint[0], item.datapoint[1]));
  563. }
  564. }
  565. else {
  566. $("#" + me.tooltip_id).remove();
  567. me.previousPoint = null;
  568. }
  569. });
  570. },
  571. get_tooltip_text: function(label, x, y) {
  572. var date = dateutil.obj_to_user(new Date(x));
  573. var value = fmt_money(y);
  574. return value + " on " + date;
  575. },
  576. get_plot_data: function() {
  577. var data = [];
  578. var me = this;
  579. $.each(this.data, function(i, item) {
  580. if (item.checked) {
  581. data.push({
  582. label: item.name,
  583. data: $.map(me.columns, function(col, idx) {
  584. if(col.formatter==me.currency_formatter && !col.hidden && col.plot!==false) {
  585. return me.get_plot_points(item, col, idx)
  586. }
  587. }),
  588. points: {show: true},
  589. lines: {show: true, fill: true},
  590. });
  591. // prepend opening
  592. data[data.length-1].data = [[dateutil.str_to_obj(me.from_date).getTime(),
  593. item.opening]].concat(data[data.length-1].data);
  594. }
  595. });
  596. return data.length ? data : false;
  597. },
  598. get_plot_options: function() {
  599. return {
  600. grid: { hoverable: true, clickable: true },
  601. xaxis: { mode: "time",
  602. min: dateutil.str_to_obj(this.from_date).getTime(),
  603. max: dateutil.str_to_obj(this.to_date).getTime() }
  604. }
  605. }
  606. });
  607. wn.views.TreeGridReport = wn.views.GridReportWithPlot.extend({
  608. make_transaction_list: function(parent_doctype, doctype) {
  609. var me = this;
  610. var tmap = {};
  611. $.each(wn.report_dump.data[doctype], function(i, v) {
  612. if(!tmap[v.parent]) tmap[v.parent] = [];
  613. tmap[v.parent].push(v);
  614. });
  615. this.tl = [];
  616. $.each(wn.report_dump.data[parent_doctype], function(i, parent) {
  617. if(tmap[parent.name]) {
  618. $.each(tmap[parent.name], function(i, d) {
  619. me.tl.push($.extend(copy_dict(parent), d));
  620. });
  621. }
  622. });
  623. },
  624. add_tree_grid_events: function() {
  625. var me = this;
  626. this.grid.onClick.subscribe(function (e, args) {
  627. if ($(e.target).hasClass("toggle")) {
  628. var item = me.dataView.getItem(args.row);
  629. if (item) {
  630. if (!item._collapsed) {
  631. item._collapsed = true;
  632. } else {
  633. item._collapsed = false;
  634. }
  635. me.dataView.updateItem(item.id, item);
  636. }
  637. e.stopImmediatePropagation();
  638. }
  639. });
  640. },
  641. tree_formatter: function (row, cell, value, columnDef, dataContext) {
  642. var me = wn.cur_grid_report;
  643. value = value.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  644. var data = me.data;
  645. var spacer = "<span style='display:inline-block;height:1px;width:" +
  646. (15 * dataContext["indent"]) + "px'></span>";
  647. var idx = me.dataView.getIdxById(dataContext.id);
  648. var link = me.tree_grid.formatter(dataContext);
  649. if(dataContext.doctype) {
  650. link += me.get_link_open_icon(dataContext.doctype, value);
  651. }
  652. if (data[idx + 1] && data[idx + 1].indent > data[idx].indent) {
  653. if (dataContext._collapsed) {
  654. return spacer + " <span class='toggle expand'></span>&nbsp;" + link;
  655. } else {
  656. return spacer + " <span class='toggle collapse'></span>&nbsp;" + link;
  657. }
  658. } else {
  659. return spacer + " <span class='toggle'></span>&nbsp;" + link;
  660. }
  661. },
  662. tree_dataview_filter: function(item) {
  663. var me = wn.cur_grid_report;
  664. if(!me.apply_filters(item)) return false;
  665. var parent = item[me.tree_grid.parent_field];
  666. while (parent) {
  667. if (me.item_by_name[parent]._collapsed) {
  668. return false;
  669. }
  670. parent = me.parent_map[parent];
  671. }
  672. return true;
  673. },
  674. prepare_tree: function(item_dt, group_dt) {
  675. var group_data = wn.report_dump.data[group_dt];
  676. var item_data = wn.report_dump.data[item_dt];
  677. // prepare map with child in respective group
  678. var me = this;
  679. var item_group_map = {};
  680. var group_ids = $.map(group_data, function(v) { return v.id; });
  681. $.each(item_data, function(i, item) {
  682. var parent = item[me.tree_grid.parent_field];
  683. if(!item_group_map[parent]) item_group_map[parent] = [];
  684. if(group_ids.indexOf(item.name)==-1) {
  685. item_group_map[parent].push(item);
  686. } else {
  687. msgprint("Ignoring Item "+ item.name.bold() +
  688. ", because a group exists with the same name!");
  689. }
  690. });
  691. // arrange items besides their parent item groups
  692. var items = [];
  693. $.each(group_data, function(i, group){
  694. group.is_group = true;
  695. items.push(group);
  696. items = items.concat(item_group_map[group.name] || []);
  697. });
  698. return items;
  699. },
  700. set_indent: function() {
  701. var me = this;
  702. $.each(this.data, function(i, d) {
  703. var indent = 0;
  704. var parent = me.parent_map[d.name];
  705. if(parent) {
  706. while(parent) {
  707. indent++;
  708. parent = me.parent_map[parent];
  709. }
  710. }
  711. d.indent = indent;
  712. });
  713. },
  714. });