Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

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