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 26 KiB

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