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.

reportview.js 16 KiB

12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
  2. // MIT License. See license.txt
  3. wn.views.ReportFactory = wn.views.Factory.extend({
  4. make: function(route) {
  5. new wn.views.ReportViewPage(route[1], route[2]);
  6. }
  7. });
  8. wn.views.ReportViewPage = Class.extend({
  9. init: function(doctype, docname) {
  10. if(!wn.model.can_get_report(doctype)) {
  11. wn.show_not_permitted(wn.get_route_str());
  12. return;
  13. };
  14. wn.require("js/slickgrid.min.js");
  15. this.doctype = doctype;
  16. this.docname = docname;
  17. this.page_name = wn.get_route_str();
  18. this.make_page();
  19. var me = this;
  20. wn.model.with_doctype(this.doctype, function() {
  21. me.make_report_view();
  22. if(me.docname) {
  23. wn.model.with_doc('Report', me.docname, function(r) {
  24. me.page.reportview.set_columns_and_filters(
  25. JSON.parse(wn.model.get("Report", me.docname)[0].json));
  26. me.page.reportview.set_route_filters();
  27. me.page.reportview.run();
  28. });
  29. } else {
  30. me.page.reportview.set_route_filters();
  31. me.page.reportview.run();
  32. }
  33. });
  34. },
  35. make_page: function() {
  36. this.page = wn.container.add_page(this.page_name);
  37. wn.ui.make_app_page({parent:this.page,
  38. single_column:true});
  39. wn.container.change_to(this.page_name);
  40. },
  41. make_report_view: function() {
  42. var module = locals.DocType[this.doctype].module;
  43. this.page.appframe.set_title(wn._(this.doctype));
  44. this.page.appframe.add_module_icon(module, this.doctype)
  45. this.page.appframe.set_views_for(this.doctype, "report");
  46. this.page.reportview = new wn.views.ReportView({
  47. doctype: this.doctype,
  48. docname: this.docname,
  49. page: this.page,
  50. wrapper: $(this.page).find(".layout-main")
  51. });
  52. }
  53. })
  54. wn.views.ReportView = wn.ui.Listing.extend({
  55. init: function(opts) {
  56. var me = this;
  57. $(this.page).find('.layout-main').html(wn._('Loading Report')+'...');
  58. $(this.page).find('.layout-main').empty();
  59. $.extend(this, opts);
  60. this.can_delete = wn.model.can_delete(me.doctype);
  61. this.tab_name = '`tab'+this.doctype+'`';
  62. this.setup();
  63. },
  64. set_init_columns: function() {
  65. // pre-select mandatory columns
  66. var columns = [['name'], ['owner']];
  67. $.each(wn.meta.docfield_list[this.doctype], function(i, df) {
  68. if(df.in_filter && df.fieldname!='naming_series' && df.fieldtype!='Table') {
  69. columns.push([df.fieldname]);
  70. }
  71. });
  72. this.columns = columns;
  73. },
  74. setup: function() {
  75. var me = this;
  76. this.page_title = wn._('Report')+ ': ' + wn._(this.docname ? (this.doctype + ' - ' + this.docname) : this.doctype);
  77. this.page.appframe.set_title(this.page_title)
  78. this.make({
  79. appframe: this.page.appframe,
  80. method: 'webnotes.widgets.reportview.get',
  81. get_args: this.get_args,
  82. parent: $(this.page).find('.layout-main'),
  83. start: 0,
  84. page_length: 20,
  85. show_filters: true,
  86. new_doctype: this.doctype,
  87. allow_delete: true,
  88. });
  89. this.make_delete();
  90. this.make_column_picker();
  91. this.make_sorter();
  92. this.make_export();
  93. this.set_init_columns();
  94. this.make_save();
  95. this.set_tag_and_status_filter();
  96. },
  97. set_init_columns: function() {
  98. // pre-select mandatory columns
  99. var columns = wn.defaults.get_default("_list_settings:" + this.doctype);
  100. if(!columns) {
  101. var columns = [['name', this.doctype],];
  102. $.each(wn.meta.docfield_list[this.doctype], function(i, df) {
  103. if(df.in_filter && df.fieldname!='naming_series'
  104. && !in_list(wn.model.no_value_type, df.fieldname)) {
  105. columns.push([df.fieldname, df.parent]);
  106. }
  107. });
  108. }
  109. this.columns = columns;
  110. },
  111. // preset columns and filters from saved info
  112. set_columns_and_filters: function(opts) {
  113. var me = this;
  114. if(opts.columns) this.columns = opts.columns;
  115. if(opts.filters) $.each(opts.filters, function(i, f) {
  116. // fieldname, condition, value
  117. me.filter_list.add_filter(f[0], f[1], f[2], f[3]);
  118. });
  119. // first sort
  120. if(opts.sort_by) this.sort_by_select.val(opts.sort_by);
  121. if(opts.sort_order) this.sort_order_select.val(opts.sort_order);
  122. // second sort
  123. if(opts.sort_by_next) this.sort_by_next_select.val(opts.sort_by_next);
  124. if(opts.sort_order_next) this.sort_order_next_select.val(opts.sort_order_next);
  125. },
  126. set_route_filters: function() {
  127. var me = this;
  128. if(wn.route_options) {
  129. $.each(wn.route_options, function(key, value) {
  130. me.filter_list.add_filter(me.doctype, key, "=", value);
  131. });
  132. wn.route_options = null;
  133. }
  134. },
  135. // build args for query
  136. get_args: function() {
  137. var me = this;
  138. return {
  139. doctype: this.doctype,
  140. fields: $.map(this.columns, function(v) { return me.get_full_column_name(v) }),
  141. order_by: this.get_order_by(),
  142. filters: this.filter_list.get_filters(),
  143. docstatus: ['0','1','2'],
  144. with_childnames: 1
  145. }
  146. },
  147. get_order_by: function() {
  148. // first
  149. var order_by = this.get_selected_table_and_column(this.sort_by_select)
  150. + ' ' + this.sort_order_select.val();
  151. // second
  152. if(this.sort_by_next_select.val()) {
  153. order_by += ', ' + this.get_selected_table_and_column(this.sort_by_next_select)
  154. + ' ' + this.sort_order_next_select.val();
  155. }
  156. return order_by;
  157. },
  158. get_selected_table_and_column: function($select) {
  159. return this.get_full_column_name([$select.find('option:selected').attr('fieldname'),
  160. $select.find('option:selected').attr('table')])
  161. },
  162. // get table_name.column_name
  163. get_full_column_name: function(v) {
  164. return (v[1] ? ('`tab' + v[1] + '`') : this.tab_name) + '.' + v[0];
  165. },
  166. // build columns for slickgrid
  167. build_columns: function() {
  168. var me = this;
  169. return $.map(this.columns, function(c) {
  170. var docfield = wn.meta.docfield_map[c[1] || me.doctype][c[0]];
  171. if(!docfield) {
  172. var docfield = wn.model.get_std_field(c[0]);
  173. if(c[0]=="name") {
  174. docfield.options = me.doctype;
  175. }
  176. }
  177. coldef = {
  178. id: c[0],
  179. field: c[0],
  180. docfield: docfield,
  181. name: wn._(docfield ? docfield.label : toTitle(c[0])),
  182. width: (docfield ? cint(docfield.width) : 120) || 120,
  183. formatter: function(row, cell, value, columnDef, dataContext) {
  184. var docfield = columnDef.docfield;
  185. return wn.format(value, docfield, null, dataContext);
  186. }
  187. }
  188. return coldef;
  189. });
  190. },
  191. // render data
  192. render_list: function() {
  193. var me = this;
  194. var columns = this.get_columns();
  195. // add sr in data
  196. $.each(this.data, function(i, v) {
  197. // add index
  198. v._idx = i+1;
  199. v.id = v._idx;
  200. });
  201. var options = {
  202. enableCellNavigation: true,
  203. enableColumnReorder: false,
  204. };
  205. if(this.slickgrid_options) {
  206. $.extend(options, this.slickgrid_options);
  207. }
  208. this.col_defs = columns;
  209. this.dataView = new Slick.Data.DataView();
  210. this.set_data(this.data);
  211. var grid_wrapper = this.$w.find('.result-list');
  212. // set height if not auto
  213. if(!options.autoHeight)
  214. grid_wrapper.css('height', '500px');
  215. this.grid = new Slick.Grid(grid_wrapper
  216. .css('border', '1px solid #ccc')
  217. .css('border-top', '0px')
  218. .get(0), this.dataView,
  219. columns, options);
  220. this.grid.setSelectionModel(new Slick.CellSelectionModel());
  221. this.grid.registerPlugin(new Slick.CellExternalCopyManager({
  222. dataItemColumnValueExtractor: function(item, columnDef, value) {
  223. return item[columnDef.field];
  224. }
  225. }));
  226. wn.slickgrid_tools.add_property_setter_on_resize(this.grid);
  227. if(this.start!=0 && !options.autoHeight) {
  228. this.grid.scrollRowIntoView(this.data.length-1);
  229. }
  230. this.grid.onDblClick.subscribe(function(e, args) {
  231. var row = me.dataView.getItem(args.row);
  232. var cell = me.grid.getColumns()[args.cell];
  233. me.edit_cell(row, cell.docfield);
  234. });
  235. this.dataView.onRowsChanged.subscribe(function (e, args) {
  236. me.grid.invalidateRows(args.rows);
  237. me.grid.render();
  238. });
  239. },
  240. edit_cell: function(row, docfield) {
  241. if(wn.model.std_fields_list.indexOf(docfield.fieldname)!==-1) {
  242. wn.throw(wn._("Cannot edit standard fields"));
  243. } else if(wn.boot.profile.can_write.indexOf(this.doctype)===-1) {
  244. wn.throw(wn._("No permission to edit"));
  245. }
  246. var me = this;
  247. var d = new wn.ui.Dialog({
  248. title: wn._("Edit") + " " + wn._(docfield.label),
  249. fields: [docfield, {"fieldtype": "Button", "label": "Update"}],
  250. });
  251. d.get_input(docfield.fieldname).val(row[docfield.fieldname]);
  252. d.get_input("update").on("click", function() {
  253. wn.call({
  254. method: "webnotes.client.set_value",
  255. args: {
  256. doctype: docfield.parent,
  257. name: row[docfield.parent===me.doctype ? "name" : docfield.parent+":name"],
  258. fieldname: docfield.fieldname,
  259. value: d.get_value(docfield.fieldname)
  260. },
  261. callback: function(r) {
  262. if(!r.exc) {
  263. d.hide();
  264. var doclist = r.message;
  265. $.each(me.dataView.getItems(), function(i, item) {
  266. if (item.name === doclist[0].name) {
  267. var new_item = $.extend({}, item, doclist[0]);
  268. $.each(doclist, function(i, doc) {
  269. if(item[doc.doctype + ":name"]===doc.name) {
  270. $.each(doc, function(k, v) {
  271. if(wn.model.std_fields_list.indexOf(k)===-1) {
  272. new_item[k] = v;
  273. }
  274. })
  275. }
  276. });
  277. me.dataView.updateItem(item.id, new_item);
  278. }
  279. });
  280. }
  281. }
  282. });
  283. });
  284. d.show();
  285. },
  286. set_data: function() {
  287. this.dataView.beginUpdate();
  288. this.dataView.setItems(this.data);
  289. this.dataView.endUpdate();
  290. },
  291. get_columns: function() {
  292. var std_columns = [{id:'_idx', field:'_idx', name: 'Sr.', width: 40, maxWidth: 40}];
  293. if(this.can_delete) {
  294. std_columns = std_columns.concat([{
  295. id:'_check', field:'_check', name: "", width: 30, maxWidth: 30,
  296. formatter: function(row, cell, value, columnDef, dataContext) {
  297. return repl("<input type='checkbox' \
  298. data-row='%(row)s' %(checked)s>", {
  299. row: row,
  300. checked: (dataContext._checked ? "checked=\"checked\"" : "")
  301. });
  302. }
  303. }]);
  304. }
  305. return std_columns.concat(this.build_columns());
  306. },
  307. // setup column picker
  308. make_column_picker: function() {
  309. var me = this;
  310. this.column_picker = new wn.ui.ColumnPicker(this);
  311. this.page.appframe.add_button(wn._('Pick Columns'), function() {
  312. me.column_picker.show(me.columns);
  313. }, 'icon-th-list');
  314. },
  315. set_tag_and_status_filter: function() {
  316. var me = this;
  317. this.$w.find('.result-list').on("click", ".label-info", function() {
  318. if($(this).attr("data-label")) {
  319. me.set_filter("_user_tags", $(this).attr("data-label"));
  320. }
  321. });
  322. this.$w.find('.result-list').on("click", "[data-workflow-state]", function() {
  323. if($(this).attr("data-workflow-state")) {
  324. me.set_filter(me.state_fieldname,
  325. $(this).attr("data-workflow-state"));
  326. }
  327. });
  328. },
  329. // setup sorter
  330. make_sorter: function() {
  331. var me = this;
  332. this.sort_dialog = new wn.ui.Dialog({title:'Sorting Preferences'});
  333. $(this.sort_dialog.body).html('<p class="help">Sort By</p>\
  334. <div class="sort-column"></div>\
  335. <div><select class="sort-order form-control" style="margin-top: 10px; width: 60%;">\
  336. <option value="asc">'+wn._('Ascending')+'</option>\
  337. <option value="desc">'+wn._('Descending')+'</option>\
  338. </select></div>\
  339. <hr><p class="help">'+wn._('Then By (optional)')+'</p>\
  340. <div class="sort-column-1"></div>\
  341. <div><select class="sort-order-1 form-control" style="margin-top: 10px; width: 60%;">\
  342. <option value="asc">'+wn._('Ascending')+'</option>\
  343. <option value="desc">'+wn._('Descending')+'</option>\
  344. </select></div><hr>\
  345. <div><button class="btn btn-info">'+wn._('Update')+'</div>');
  346. // first
  347. this.sort_by_select = new wn.ui.FieldSelect($(this.sort_dialog.body).find('.sort-column'),
  348. this.doctype).$select;
  349. this.sort_by_select.css('width', '60%');
  350. this.sort_order_select = $(this.sort_dialog.body).find('.sort-order');
  351. // second
  352. this.sort_by_next_select = new wn.ui.FieldSelect($(this.sort_dialog.body).find('.sort-column-1'),
  353. this.doctype, null, true).$select;
  354. this.sort_by_next_select.css('width', '60%');
  355. this.sort_order_next_select = $(this.sort_dialog.body).find('.sort-order-1');
  356. // initial values
  357. this.sort_by_select.val(this.doctype + '.modified');
  358. this.sort_order_select.val('desc');
  359. this.sort_by_next_select.val('');
  360. this.sort_order_next_select.val('desc');
  361. // button actions
  362. this.page.appframe.add_button(wn._('Sort By'), function() {
  363. me.sort_dialog.show();
  364. }, 'icon-arrow-down');
  365. $(this.sort_dialog.body).find('.btn-info').click(function() {
  366. me.sort_dialog.hide();
  367. me.run();
  368. });
  369. },
  370. // setup export
  371. make_export: function() {
  372. var me = this;
  373. var export_btn = this.page.appframe.add_button(wn._('Export'), function() {
  374. var args = me.get_args();
  375. args.cmd = 'webnotes.widgets.reportview.export_query'
  376. open_url_post(wn.request.url, args);
  377. }, 'icon-download-alt');
  378. wn.utils.disable_export_btn(export_btn);
  379. },
  380. // save
  381. make_save: function() {
  382. var me = this;
  383. if(wn.user.is_report_manager()) {
  384. this.page.appframe.add_button(wn._('Save'), function() {
  385. // name
  386. if(me.docname) {
  387. var name = me.docname
  388. } else {
  389. var name = prompt(wn._('Select Report Name'));
  390. if(!name) {
  391. return;
  392. }
  393. }
  394. // callback
  395. return wn.call({
  396. method: 'webnotes.widgets.reportview.save_report',
  397. args: {
  398. name: name,
  399. doctype: me.doctype,
  400. json: JSON.stringify({
  401. filters: me.filter_list.get_filters(),
  402. columns: me.columns,
  403. sort_by: me.sort_by_select.val(),
  404. sort_order: me.sort_order_select.val(),
  405. sort_by_next: me.sort_by_next_select.val(),
  406. sort_order_next: me.sort_order_next_select.val()
  407. })
  408. },
  409. callback: function(r) {
  410. if(r.exc) {
  411. msgprint(wn._("Report was not saved (there were errors)"));
  412. return;
  413. }
  414. if(r.message != me.docname)
  415. wn.set_route('Report', me.doctype, r.message);
  416. }
  417. });
  418. }, 'icon-upload');
  419. }
  420. },
  421. make_delete: function() {
  422. var me = this;
  423. if(this.can_delete) {
  424. $(this.page).on("click", "input[type='checkbox'][data-row]", function() {
  425. me.data[$(this).attr("data-row")]._checked
  426. = this.checked ? true : false;
  427. });
  428. this.page.appframe.add_button(wn._("Delete"), function() {
  429. var delete_list = []
  430. $.each(me.data, function(i, d) {
  431. if(d._checked) {
  432. if(d.name)
  433. delete_list.push(d.name);
  434. }
  435. });
  436. if(!delete_list.length)
  437. return;
  438. if(wn.confirm(wn._("This is PERMANENT action and you cannot undo. Continue?"),
  439. function() {
  440. return wn.call({
  441. method: 'webnotes.widgets.reportview.delete_items',
  442. args: {
  443. items: delete_list,
  444. doctype: me.doctype
  445. },
  446. callback: function() {
  447. me.refresh();
  448. }
  449. });
  450. }));
  451. }, 'icon-remove');
  452. }
  453. },
  454. });
  455. wn.ui.ColumnPicker = Class.extend({
  456. init: function(list) {
  457. this.list = list;
  458. this.doctype = list.doctype;
  459. this.selects = {};
  460. },
  461. show: function(columns) {
  462. wn.require('lib/js/lib/jquery/jquery.ui.interactions.min.js');
  463. var me = this;
  464. if(!this.dialog) {
  465. this.dialog = new wn.ui.Dialog({
  466. title: wn._("Pick Columns"),
  467. width: '400'
  468. });
  469. }
  470. $(this.dialog.body).html('<div class="help">'+wn._("Drag to sort columns")+'</div>\
  471. <div class="column-list"></div>\
  472. <div><button class="btn btn-default btn-add"><i class="icon-plus"></i>\
  473. '+wn._("Add Column")+'</button></div>\
  474. <hr>\
  475. <div><button class="btn btn-info">'+wn._("Update")+'</div>');
  476. // show existing
  477. $.each(columns, function(i, c) {
  478. me.add_column(c);
  479. });
  480. $(this.dialog.body).find('.column-list').sortable();
  481. // add column
  482. $(this.dialog.body).find('.btn-add').click(function() {
  483. me.add_column(['name']);
  484. });
  485. // update
  486. $(this.dialog.body).find('.btn-info').click(function() {
  487. me.dialog.hide();
  488. // selected columns as list of [column_name, table_name]
  489. var columns = [];
  490. $(me.dialog.body).find('select').each(function() {
  491. var $selected = $(this).find('option:selected');
  492. columns.push([$selected.attr('fieldname'),
  493. $selected.attr('table')]);
  494. });
  495. wn.defaults.set_default("_list_settings:" + me.doctype, columns);
  496. me.list.columns = columns;
  497. me.list.run();
  498. });
  499. this.dialog.show();
  500. },
  501. add_column: function(c) {
  502. var w = $('<div style="padding: 5px; background-color: #eee; \
  503. width: 90%; margin-bottom: 10px; border-radius: 3px; cursor: move;">\
  504. <img src="lib/images/ui/drag-handle.png" style="margin-right: 10px;">\
  505. <a class="close" style="margin-top: 5px;">&times</a>\
  506. </div>')
  507. .appendTo($(this.dialog.body).find('.column-list'));
  508. var fieldselect = new wn.ui.FieldSelect(w, this.doctype);
  509. fieldselect.$select.css({"display": "inline"});
  510. fieldselect.$select
  511. .css({width: '70%', 'margin-top':'5px'})
  512. .val((c[1] || this.doctype) + "." + c[0]);
  513. w.find('.close').click(function() {
  514. $(this).parent().remove();
  515. });
  516. }
  517. });