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

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