選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 
 

388 行
10 KiB

  1. // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
  2. // MIT License. See license.txt
  3. wn.provide("wn.views");
  4. wn.provide("wn.query_reports");
  5. wn.standard_pages["query-report"] = function() {
  6. var wrapper = wn.container.add_page('query-report');
  7. wn.require("js/slickgrid.min.js");
  8. wn.ui.make_app_page({
  9. parent: wrapper,
  10. title: 'Query Report',
  11. single_column: true
  12. });
  13. wn.query_report = new wn.views.QueryReport({
  14. parent: wrapper,
  15. });
  16. $(wrapper).bind("show", function() {
  17. wn.query_report.load();
  18. });
  19. }
  20. wn.views.QueryReport = Class.extend({
  21. init: function(opts) {
  22. $.extend(this, opts);
  23. // globalify for slickgrid
  24. this.appframe = this.parent.appframe;
  25. this.parent.query_report = this;
  26. this.make();
  27. this.load();
  28. },
  29. slickgrid_options: {
  30. enableColumnReorder: false,
  31. showHeaderRow: true,
  32. headerRowHeight: 30,
  33. explicitInitialization: true,
  34. multiColumnSort: true
  35. },
  36. make: function() {
  37. this.wrapper = $("<div>").appendTo($(this.parent).find(".layout-main"));
  38. $('<div class="waiting-area" style="display: none;"></div>\
  39. <div class="no-report-area well" style="display: none;">\
  40. </div>\
  41. <div class="results" style="display: none;">\
  42. <div class="result-area" style="height:400px; \
  43. border: 1px solid #aaa;"></div>\
  44. <p class="help"><br>\
  45. '+wn._('For comparative filters, start with')+' ">" or "<", e.g. >5 or >01-02-2012\
  46. <br>'+wn._('For ranges')+' ('+wn._('values and dates')+') use ":", \
  47. e.g. "5:10" (to filter values between 5 & 10)</p>\
  48. </div>').appendTo(this.wrapper);
  49. this.make_toolbar();
  50. },
  51. make_toolbar: function() {
  52. var me = this;
  53. this.appframe.add_button(wn._('Refresh'), function() { me.refresh(); }, "icon-refresh")
  54. .addClass("btn-success");
  55. // Edit
  56. var edit_btn = this.appframe.add_button(wn._('Edit'), function() {
  57. wn.set_route("Form", "Report", me.report_name);
  58. }, "icon-edit");
  59. if(!in_list(user_roles, "System Manager")) {
  60. edit_btn.prop("disabled", true)
  61. .attr("title", wn._("Only System Manager can create / edit reports"));
  62. }
  63. var export_btn = this.appframe.add_button(wn._('Export'), function() { me.export_report(); },
  64. "icon-download");
  65. wn.utils.disable_export_btn(export_btn);
  66. },
  67. load: function() {
  68. // load from route
  69. var route = wn.get_route();
  70. var me = this;
  71. if(route[1]) {
  72. if(me.report_name!=route[1]) {
  73. me.report_name = route[1];
  74. this.wrapper.find(".no-report-area").toggle(false);
  75. me.appframe.set_title(wn._("Query Report")+": " + me.report_name);
  76. if(!wn.query_reports[me.report_name]) {
  77. return wn.call({
  78. method:"webnotes.widgets.query_report.get_script",
  79. args: {
  80. report_name: me.report_name
  81. },
  82. callback: function(r) {
  83. wn.dom.eval(r.message || "");
  84. me.setup_filters();
  85. me.refresh();
  86. }
  87. })
  88. } else {
  89. me.setup_filters();
  90. me.refresh();
  91. }
  92. }
  93. } else {
  94. var msg = wn._("No Report Loaded. Please use query-report/[Report Name] to run a report.")
  95. this.wrapper.find(".no-report-area").html(msg).toggle(true);
  96. }
  97. },
  98. setup_filters: function() {
  99. this.clear_filters();
  100. var me = this;
  101. $.each(wn.query_reports[this.report_name].filters || [], function(i, df) {
  102. var f = me.appframe.add_field(df);
  103. $(f.wrapper).addClass("filters pull-left");
  104. me.filters.push(f);
  105. if(df["default"]) {
  106. f.set_input(df["default"]);
  107. }
  108. if(df.get_query) f.get_query = df.get_query;
  109. });
  110. this.set_filters_by_name();
  111. },
  112. clear_filters: function() {
  113. this.filters = [];
  114. this.appframe.$w.find('.navbar .filters').remove();
  115. },
  116. set_filters_by_name: function() {
  117. this.filters_by_name = {};
  118. for(var i in this.filters) {
  119. this.filters_by_name[this.filters[i].df.fieldname] = this.filters[i];
  120. }
  121. },
  122. refresh: function() {
  123. // Run
  124. var me =this;
  125. this.waiting = wn.messages.waiting(this.wrapper.find(".waiting-area").toggle(true),
  126. "Loading Report...");
  127. var filters = {};
  128. $.each(this.filters || [], function(i, f) {
  129. filters[f.df.fieldname] = f.get_parsed_value();
  130. })
  131. return wn.call({
  132. method: "webnotes.widgets.query_report.run",
  133. type: "GET",
  134. args: {
  135. "report_name": me.report_name,
  136. filters: filters
  137. },
  138. callback: function(r) {
  139. me.make_results(r.message.result, r.message.columns);
  140. }
  141. })
  142. },
  143. make_results: function(result, columns) {
  144. this.wrapper.find(".waiting-area").empty().toggle(false);
  145. this.wrapper.find(".results").toggle(true);
  146. this.make_columns(columns);
  147. this.make_data(result, columns);
  148. this.render(result, columns);
  149. },
  150. render: function(result, columns) {
  151. this.columnFilters = {};
  152. this.make_dataview();
  153. this.id = wn.dom.set_unique_id(this.wrapper.find(".result-area").get(0));
  154. this.grid = new Slick.Grid("#"+this.id, this.dataView, this.columns,
  155. this.slickgrid_options);
  156. this.grid.setSelectionModel(new Slick.CellSelectionModel());
  157. this.grid.registerPlugin(new Slick.CellExternalCopyManager({
  158. dataItemColumnValueExtractor: function(item, columnDef, value) {
  159. return item[columnDef.field];
  160. }
  161. }));
  162. this.setup_header_row();
  163. this.grid.init();
  164. this.setup_sort();
  165. },
  166. make_columns: function(columns) {
  167. this.columns = [{id: "_id", field: "_id", name: "Sr No", width: 60}]
  168. .concat($.map(columns, function(c) {
  169. var col = {name:c, id: c, field: c, sortable: true, width: 80}
  170. if(c.indexOf(":")!=-1) {
  171. var opts = c.split(":");
  172. var df = {
  173. label: opts[0],
  174. fieldtype: opts[1],
  175. width: opts[2]
  176. }
  177. if(!df.fieldtype)
  178. df.fieldtype="Data";
  179. if(df.fieldtype.indexOf("/")!=-1) {
  180. var tmp = df.fieldtype.split("/");
  181. df.fieldtype = tmp[0];
  182. df.options = tmp[1];
  183. }
  184. col.df = df;
  185. col.formatter = function(row, cell, value, columnDef, dataContext) {
  186. return wn.format(value, columnDef.df, null, dataContext);
  187. }
  188. // column parameters
  189. col.name = col.id = col.field = df.label;
  190. col.fieldtype = opts[1];
  191. // width
  192. if(df.width) {
  193. col.width=parseInt(df.width);
  194. }
  195. } else {
  196. col.df = {
  197. label: c,
  198. fieldtype: "Data"
  199. }
  200. }
  201. col.name = toTitle(col.name.replace(/ /g, " "))
  202. return col
  203. }));
  204. },
  205. make_data: function(result, columns) {
  206. var me = this;
  207. this.data = [];
  208. for(var row_idx=0, l=result.length; row_idx < l; row_idx++) {
  209. var row = result[row_idx];
  210. var newrow = {};
  211. for(var i=1, j=this.columns.length; i<j; i++) {
  212. newrow[this.columns[i].field] = row[i-1];
  213. };
  214. newrow._id = row_idx + 1;
  215. newrow.id = newrow.name ? newrow.name : ("_" + newrow._id);
  216. this.data.push(newrow);
  217. }
  218. },
  219. make_dataview: function() {
  220. // initialize the model
  221. this.dataView = new Slick.Data.DataView({ inlineFilters: true });
  222. this.dataView.beginUpdate();
  223. this.dataView.setItems(this.data);
  224. this.dataView.setFilter(this.inline_filter);
  225. this.dataView.endUpdate();
  226. var me = this;
  227. this.dataView.onRowCountChanged.subscribe(function (e, args) {
  228. me.grid.updateRowCount();
  229. me.grid.render();
  230. });
  231. this.dataView.onRowsChanged.subscribe(function (e, args) {
  232. me.grid.invalidateRows(args.rows);
  233. me.grid.render();
  234. });
  235. },
  236. inline_filter: function (item) {
  237. var me = wn.container.page.query_report;
  238. for (var columnId in me.columnFilters) {
  239. if (columnId !== undefined && me.columnFilters[columnId] !== "") {
  240. var c = me.grid.getColumns()[me.grid.getColumnIndex(columnId)];
  241. if (!me.compare_values(item[c.field], me.columnFilters[columnId],
  242. me.columns[me.grid.getColumnIndex(columnId)])) {
  243. return false;
  244. }
  245. }
  246. }
  247. return true;
  248. },
  249. compare_values: function(value, filter, columnDef) {
  250. var invert = false;
  251. // check if invert
  252. if(filter[0]=="!") {
  253. invert = true;
  254. filter = filter.substr(1);
  255. }
  256. var out = false;
  257. var cond = "=="
  258. // parse condition
  259. if(filter[0]==">") {
  260. filter = filter.substr(1);
  261. cond = ">"
  262. } else if(filter[0]=="<") {
  263. filter = filter.substr(1);
  264. cond = "<"
  265. }
  266. if(in_list(['Float', 'Currency', 'Int', 'Date'], columnDef.df.fieldtype)) {
  267. // non strings
  268. if(filter.indexOf(":")==-1) {
  269. if(columnDef.df.fieldtype=="Date") {
  270. filter = dateutil.user_to_str(filter);
  271. }
  272. if(in_list(["Float", "Currency", "Int"], columnDef.df.fieldtype)) {
  273. value = flt(value);
  274. filter = flt(filter);
  275. }
  276. out = eval("value" + cond + "filter");
  277. } else {
  278. // range
  279. filter = filter.split(":");
  280. if(columnDef.df.fieldtype=="Date") {
  281. filter[0] = dateutil.user_to_str(filter[0]);
  282. filter[1] = dateutil.user_to_str(filter[1]);
  283. }
  284. if(in_list(["Float", "Currency", "Int"], columnDef.df.fieldtype)) {
  285. value = flt(value);
  286. filter[0] = flt(filter[0]);
  287. filter[1] = flt(filter[1]);
  288. }
  289. out = value >= filter[0] && value <= filter[1];
  290. }
  291. } else {
  292. // string
  293. value = value + "";
  294. value = value.toLowerCase();
  295. filter = filter.toLowerCase();
  296. out = value.indexOf(filter) != -1;
  297. }
  298. if(invert)
  299. return !out;
  300. else
  301. return out;
  302. },
  303. setup_header_row: function() {
  304. var me = this;
  305. $(this.grid.getHeaderRow()).delegate(":input", "change keyup", function (e) {
  306. var columnId = $(this).data("columnId");
  307. if (columnId != null) {
  308. me.columnFilters[columnId] = $.trim($(this).val());
  309. me.dataView.refresh();
  310. }
  311. });
  312. this.grid.onHeaderRowCellRendered.subscribe(function(e, args) {
  313. $(args.node).empty();
  314. $("<input type='text'>")
  315. .data("columnId", args.column.id)
  316. .val(me.columnFilters[args.column.id])
  317. .appendTo(args.node);
  318. });
  319. },
  320. setup_sort: function() {
  321. var me = this;
  322. this.grid.onSort.subscribe(function (e, args) {
  323. var cols = args.sortCols;
  324. me.data.sort(function (dataRow1, dataRow2) {
  325. for (var i = 0, l = cols.length; i < l; i++) {
  326. var field = cols[i].sortCol.field;
  327. var sign = cols[i].sortAsc ? 1 : -1;
  328. var value1 = dataRow1[field], value2 = dataRow2[field];
  329. var result = (value1 == value2 ? 0 : (value1 > value2 ? 1 : -1)) * sign;
  330. if (result != 0) {
  331. return result;
  332. }
  333. }
  334. return 0;
  335. });
  336. me.dataView.beginUpdate();
  337. me.dataView.setItems(me.data);
  338. me.dataView.endUpdate();
  339. me.dataView.refresh();
  340. });
  341. },
  342. export_report: function() {
  343. var result = $.map(wn.slickgrid_tools.get_view_data(this.columns, this.dataView),
  344. function(row) {
  345. return [row.splice(1)];
  346. });
  347. this.title = this.report_name;
  348. wn.tools.downloadify(result, ["Report Manager", "System Manager"], this);
  349. return false;
  350. }
  351. })