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.

grid_report.js 25 KiB

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