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

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