Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

reportview.js 17 KiB

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