您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

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