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