Frappe Datatable in Query Reports (#5054)

* wip

* import DataTable in reportview

* first cut

* [wip] Tree in Query Report

* Remove SlickGrid

* Add beta message

* fix codacy
This commit is contained in:
Faris Ansari 2018-02-27 16:08:32 +05:30 committed by Nabin Hait
parent 9bb5ab237f
commit a522d970e3
12 changed files with 5317 additions and 910 deletions

View file

@ -351,11 +351,7 @@
],
"css/report.min.css": [
"public/less/report.less",
"public/css/tree_grid.css",
"public/js/lib/slickgrid/slick.grid.css",
"public/js/lib/slickgrid/slick-default-theme.css",
"public/css/slickgrid.css"
"public/css/tree_grid.css"
],
"js/report.min.js": [
"public/js/lib/clusterize.min.js",
@ -366,16 +362,7 @@
"public/js/frappe/views/reports/query_report.js",
"public/js/frappe/views/reports/grid_report.js",
"public/js/frappe/views/reports/print_grid.html",
"public/js/frappe/views/reports/print_tree.html",
"public/js/lib/slickgrid/jquery.event.drag.js",
"public/js/lib/slickgrid/plugins/slick.cellrangedecorator.js",
"public/js/lib/slickgrid/plugins/slick.cellrangeselector.js",
"public/js/lib/slickgrid/plugins/slick.cellselectionmodel.js",
"public/js/lib/slickgrid/plugins/slick.cellexternalcopymanager.js",
"public/js/lib/slickgrid/slick.core.js",
"public/js/lib/slickgrid/slick.grid.js",
"public/js/lib/slickgrid/slick.dataview.js"
"public/js/frappe/views/reports/print_tree.html"
],
"js/web_form.min.js": [
"website/js/web_form.js",

View file

@ -489,15 +489,19 @@ frappe.Logger = class {
*
* @param {string} name - Name of the logger.
*/
constructor (name) {
constructor (name, level) {
if ( typeof name !== 'string' )
throw new frappe.TypeError(`Expected string for name, got ${typeof name} instead.`)
this.name = name
if ( frappe.boot.developer_mode )
this.level = frappe.Logger.ERROR
else
this.level = frappe.Logger.NOTSET
this.level = level
if ( !this.level ) {
if ( frappe.boot.developer_mode )
this.level = frappe.Logger.ERROR
else
this.level = frappe.Logger.NOTSET
}
this.format = frappe.Logger.FORMAT
}
@ -506,9 +510,9 @@ frappe.Logger = class {
*
* @param {string} name - Name of the logger.
*/
static get (name) {
static get (name, level) {
if ( !(name in frappe.loggers) )
frappe.loggers[name] = new frappe.Logger(name)
frappe.loggers[name] = new frappe.Logger(name, level)
return frappe.loggers[name]
}
@ -541,7 +545,7 @@ frappe.Logger.FORMAT = '{time} {name}'
// frappe.chat
frappe.provide('frappe.chat')
frappe.log = frappe.Logger.get('frappe.chat')
frappe.log = frappe.Logger.get('frappe.chat', frappe.Logger.NOTSET)
// frappe.chat.profile
frappe.provide('frappe.chat.profile')

View file

@ -20,7 +20,6 @@ frappe.views.BaseList = class BaseList {
this.setup_fields,
// make view
this.setup_page,
this.setup_page_head,
this.setup_side_bar,
this.setup_main_section,
this.setup_view,
@ -140,10 +139,10 @@ frappe.views.BaseList = class BaseList {
}
setup_page() {
this.parent.list_view = this;
this.page = this.parent.page;
this.$page = $(this.parent);
this.page.page_form.removeClass('row').addClass('flex');
this.setup_page_head();
}
setup_page_head() {
@ -165,9 +164,12 @@ frappe.views.BaseList = class BaseList {
}
this.menu_items.map(item => {
if (item.condition && item.condition() === false) {
return;
}
const $item = this.page.add_menu_item(item.label, item.action, item.standard);
if (item.class) {
$item.addClass(item.class);
$item && $item.addClass(item.class);
}
});
}
@ -555,7 +557,7 @@ class FilterArea {
if(this.list_view.custom_filter_configs) {
this.list_view.custom_filter_configs.forEach(config => {
config.onchange = () => this.refresh_list_view();
})
});
fields = fields.concat(this.list_view.custom_filter_configs);
}

View file

@ -56,6 +56,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.patch_refresh_and_load_lib();
}
setup_page() {
this.parent.list_view = this;
super.setup_page();
}
set_fields() {
let fields = [].concat(
frappe.model.std_fields_list,

View file

@ -166,7 +166,9 @@ $.extend(frappe.model, {
name: name
},
freeze: true,
callback: function(r) { callback(name, r); }
callback: function(r) {
callback && callback(name, r);
}
});
}
},

View file

@ -529,9 +529,15 @@ frappe.ui.Page = Class.extend({
this.fields_dict[df.fieldname || df.label] = f;
return f;
},
clear_fields: function() {
this.page_form.empty();
},
show_form: function() {
this.page_form.removeClass("hide");
},
hide_form: function() {
this.page_form.addClass("hide");
},
get_form_values: function() {
var values = {};
this.page_form.fields_dict.forEach(function(field, key) {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,968 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.provide("frappe.views");
frappe.provide("frappe.query_reports");
frappe.provide("frappe.ui.graphs");
frappe.standard_pages["query-report"] = function() {
var wrapper = frappe.container.add_page('query-report');
frappe.ui.make_app_page({
parent: wrapper,
title: __('Query Report'),
single_column: true,
});
frappe.query_report = new frappe.views.QueryReport({
parent: wrapper,
});
$(wrapper).bind("show", function() {
frappe.query_report.load();
});
}
frappe.views.QueryReport = Class.extend({
init: function(opts) {
$.extend(this, opts);
this.flags = {};
// globalify for slickgrid
this.page = this.parent.page;
this.parent.query_report = this;
this.make();
},
slickgrid_options: {
enableColumnReorder: false,
showHeaderRow: true,
headerRowHeight: 30,
explicitInitialization: true,
multiColumnSort: true
},
make: function() {
var me = this;
this.wrapper = $("<div>").appendTo(this.page.main);
$('<div class="waiting-area" style="display: none;"></div>\
<div class="no-report-area msg-box no-border" style="display: none;"></div>\
<div class="chart-area" style="border-bottom: 1px solid #d1d8dd; margin: 0px 3%"></div>\
<div class="results" style="display: none;">\
<div class="result-area" style="height:70vh;"></div>\
<button class="btn btn-secondary btn-default btn-xs expand-all hidden" style="margin: 10px;">'+__('Expand All')+'</button>\
<button class="btn btn-secondary btn-default btn-xs collapse-all hidden" style="margin: 10px; margin-left: 0px;">'+__('Collapse All')+'</button>\
<p class="help-msg alert alert-warning text-center" style="margin: 15px; margin-top: 0px;"></p>\
<p class="msg-box small">\
'+__('For comparative filters, start with')+' ">" or "<" or "!", e.g. >5 or >01-02-2012 or !0\
<br>'+__('For ranges')+' ('+__('values and dates')+') use ":", \
e.g. "5:10" (' + __("to filter values between 5 & 10") + ')</p>\
</div>').appendTo(this.wrapper);
this.wrapper.find(".expand-all").on("click", function() { me.toggle_all(false);});
this.wrapper.find(".collapse-all").on("click", function() { me.toggle_all(true);});
this.chart_area = this.wrapper.find(".chart-area");
this.make_toolbar();
},
toggle_expand_collapse_buttons: function(show) {
this.wrapper.find(".expand-all, .collapse-all").toggleClass('hidden', !!!show);
},
make_toolbar: function() {
var me = this;
this.page.set_secondary_action(__('Refresh'), function() { me.refresh(); });
// Edit
this.page.add_menu_item(__('Edit'), function() {
if(!frappe.user.is_report_manager()) {
frappe.msgprint(__("You are not allowed to create / edit reports"));
return false;
}
frappe.set_route("Form", "Report", me.report_name);
}, true);
this.page.add_menu_item(__("Print"), function() {
frappe.ui.get_print_settings(false, function(print_settings) {
me.print_settings = print_settings;
me.print_report();
}, me.report_doc.letter_head);
}, true);
this.page.add_menu_item(__("PDF"), function() {
frappe.ui.get_print_settings(true, function(print_settings) {
me.print_settings = print_settings;
me.pdf_report();
}, me.report_doc.letter_head);
}, true);
this.page.add_menu_item(__('Export'), function() {
me.make_export();
}, true);
this.page.add_menu_item(__("Setup Auto Email"), function() {
frappe.set_route('List', 'Auto Email Report', {'report' : me.report_name});
}, true);
if(frappe.model.can_set_user_permissions("Report")) {
this.page.add_menu_item(__("User Permissions"), function() {
frappe.route_options = {
doctype: "Report",
name: me.report_name
};
frappe.set_route('List', 'User Permission');
}, true);
}
// add to desktop
this.page.add_menu_item(__("Add to Desktop"), function() {
frappe.add_to_desktop(me.report_name, null, me.report_name);
}, true);
},
load: function() {
// load from route
var route = frappe.get_route();
var me = this;
if(route[1]) {
if((me.report_name!=route[1]) || frappe.route_options) {
me.report_name = route[1];
this.wrapper.find(".no-report-area").toggle(false);
me.page.set_title(__(me.report_name));
frappe.model.with_doc("Report", me.report_name, function() {
me.report_doc = frappe.get_doc("Report", me.report_name);
frappe.model.with_doctype(me.report_doc.ref_doctype, function() {
var module = locals.DocType[me.report_doc.ref_doctype].module;
frappe.breadcrumbs.add(module)
if(!frappe.query_reports[me.report_name]) {
return frappe.call({
method:"frappe.desk.query_report.get_script",
args: {
report_name: me.report_name
},
callback: function(r) {
frappe.dom.eval(r.message.script || "");
frappe.after_ajax(function() {
var report_settings = frappe.query_reports[me.report_name];
me.html_format = r.message.html_format;
report_settings["html_format"] = r.message.html_format;
me.setup_report();
});
}
});
} else {
me.setup_report();
}
});
});
}
} else {
var msg = __("No Report Loaded. Please use query-report/[Report Name] to run a report.")
this.wrapper.find(".no-report-area").html(msg).toggle(true);
}
},
setup_report: function() {
var me = this;
this.page.set_title(__(this.report_name));
this.page.clear_inner_toolbar();
this.setup_filters();
this.chart_area.toggle(false);
this.toggle_expand_collapse_buttons(false);
this.is_tree_report = false;
var report_settings = frappe.query_reports[this.report_name];
$.when(function() {
if (report_settings.onload) {
return report_settings.onload(me);
}
}()).then(function() {
me.refresh();
});
},
print_report: function() {
if(!frappe.model.can_print(this.report_doc.ref_doctype)) {
frappe.msgprint(__("You are not allowed to print this report"));
return false;
}
if(this.html_format) {
var content = frappe.render(this.html_format, {
data: frappe.slickgrid_tools.get_filtered_items(this.dataView),
filters: this.get_values(),
report: this,
data_to_be_printed: this.data_to_be_printed
});
frappe.render_grid({
content: content,
title: __(this.report_name),
print_settings: this.print_settings,
columns: this.columns
});
} else {
frappe.render_grid({
grid: this.grid,
report: this,
title: __(this.report_name),
print_settings: this.print_settings,
});
}
},
pdf_report: function() {
var me = this;
var base_url = frappe.urllib.get_base_url();
var print_css = frappe.boot.print_css;
if(!frappe.model.can_print(this.report_doc.ref_doctype)) {
frappe.msgprint(__("You are not allowed to make PDF for this report"));
return false;
}
var orientation = this.print_settings.orientation;
var landscape = orientation == "Landscape" ? true: false
var columns = this.grid.getColumns();
if(this.html_format) {
var content = frappe.render(this.html_format, {
data: frappe.slickgrid_tools.get_filtered_items(this.dataView),
filters:this.get_values(),
report:this,
data_to_be_printed: this.data_to_be_printed
});
//Render Report in HTML
var html = frappe.render_template("print_template", {
content:content,
title:__(this.report_name),
base_url: base_url,
print_css: print_css,
print_settings: this.print_settings,
landscape: landscape,
columns: columns
});
} else {
// rows filtered by inline_filter of slickgrid
var visible_idx = frappe.slickgrid_tools
.get_view_data(this.columns, this.dataView)
.map(row => row[0]).filter(idx => idx !== 'Sr No');
var data = this.grid.getData().getItems();
data = data.filter(d => visible_idx.includes(d._id));
var content = frappe.render_template("print_grid", {
columns:columns,
data:data,
title:__(this.report_name)
})
//Render Report in HTML
var html = frappe.render_template("print_template",{
content:content,
title:__(this.report_name),
base_url: base_url,
print_css: print_css,
print_settings: this.print_settings,
landscape: landscape,
columns: columns
});
}
var orientation = this.print_settings.orientation;
this.open_pdf_report(html, orientation)
},
open_pdf_report: function(html, orientation) {
//Create a form to place the HTML content
var formData = new FormData();
//Push the HTML content into an element
formData.append("html", html);
formData.append("orientation", orientation);
var blob = new Blob([], { type: "text/xml"});
//formData.append("webmasterfile", blob);
formData.append("blob", blob);
var xhr = new XMLHttpRequest();
xhr.open("POST", '/api/method/frappe.utils.print_format.report_to_pdf');
xhr.setRequestHeader("X-Frappe-CSRF-Token", frappe.csrf_token);
xhr.responseType = "arraybuffer";
xhr.onload = function(success) {
if (this.status === 200) {
var blob = new Blob([success.currentTarget.response], {type: "application/pdf"});
var objectUrl = URL.createObjectURL(blob);
//Open report in a new window
window.open(objectUrl);
}
};
xhr.send(formData);
},
setup_filters: function() {
if(this.setting_filters) return;
this.clear_filters();
var me = this;
$.each(frappe.query_reports[this.report_name].filters || [], function(i, df) {
if(df.fieldtype==="Break") {
me.page.add_break();
} else {
var f = me.page.add_field(df);
$(f.wrapper).addClass("filters pull-left");
me.filters.push(f);
if(df["default"]) {
f.set_input(df["default"]);
}
if(df.fieldtype=="Check") {
$(f.wrapper).find("input[type='checkbox']");
}
if(df.get_query) f.get_query = df.get_query;
if(df.on_change) f.on_change = df.on_change;
df.onchange = () => {
if(!me.flags.filters_set) {
// don't trigger change while setting filters
return;
}
if (f.on_change) {
f.on_change(me);
} else {
me.trigger_refresh();
}
}
}
});
// hide page form if no filters
var $filters = $(this.parent).find('.page-form .filters');
$(this.parent).find('.page-form').toggle($filters.length ? true : false);
// set the field 'query_report_filters_by_name' first as they can be used in
// setting/triggering the filters
this.set_filters_by_name();
this.setting_filters = true;
this.set_route_filters();
this.setting_filters = false;
this.flags.filters_set = true;
},
clear_filters: function() {
this.filters = [];
$(this.parent).find('.page-form .filters').remove();
},
set_filters_by_name: function() {
frappe.query_report_filters_by_name = {};
for(var i in this.filters) {
frappe.query_report_filters_by_name[this.filters[i].df.fieldname] = this.filters[i];
}
},
set_route_filters: function() {
var me = this;
if(frappe.route_options) {
const fields = Object.keys(frappe.route_options);
const filters_to_set = this.filters.filter(f => fields.includes(f.df.fieldname));
const promises = filters_to_set.map(f => {
return () => {
const value = frappe.route_options[f.df.fieldname];
return f.set_value(value);
}
});
promises.push(() => {
frappe.route_options = null;
});
return frappe.run_serially(promises);
}
},
refresh: function() {
// throttle
// stop refresh from being called multiple times (from triggers ?)
if (!this.request_refresh) {
this.request_refresh = setTimeout(() => {
this._refresh();
this.request_refresh = null;
}, 300);
}
},
_refresh: function() {
// Run
var me = this;
this.wrapper.find(".results").toggle(false);
try {
var filters = this.get_values(true);
} catch(e) {
// don't run report
return;
}
this.waiting = frappe.messages.waiting(this.wrapper.find(".waiting-area").empty().toggle(true),
__("Loading Report") + "...");
this.wrapper.find(".no-report-area").toggle(false);
if (this.report_ajax) {
// abort previous request
this.report_ajax.abort();
}
this.chart_area.toggle(false);
this.report_ajax = frappe.call({
method: "frappe.desk.query_report.run",
type: "GET",
args: {
"report_name": me.report_name,
filters: filters
},
callback: function(r) {
me.report_ajax = undefined;
me.make_results(r.message);
}
});
return this.report_ajax;
},
trigger_refresh: function() {
var me = this;
var filters = me.get_values();
// check if required filters are not missing
var missing = false;
$.each(me.filters, function(k, _f) {
if (_f.df.reqd && !filters[_f.df.fieldname]) {
missing = true;
return;
}
});
if (!missing) {
me.refresh();
}
},
get_values: function(raise) {
var filters = {};
var mandatory_fields = [];
$.each(this.filters || [], function(i, f) {
var v = f.get_value();
// TODO: hidden fields dont have $input
if(f.df.hidden) v = f.value;
if(v === '%') v = null;
if(f.df.reqd && !v) mandatory_fields.push(f.df.label);
if(v) filters[f.df.fieldname] = v;
})
if(raise && mandatory_fields.length) {
this.chart_area.hide();
this.wrapper.find(".waiting-area").empty().toggle(false);
this.wrapper.find(".no-report-area").html(__("Please set filters")).toggle(true);
if(raise) {
console.log('filter missing: ' + mandatory_fields);
throw "Filters required";
}
}
return filters;
},
make_results: function(res) {
this.wrapper.find(".waiting-area, .no-report-area").empty().toggle(false);
this.wrapper.find(".results").toggle(true);
this.make_columns(res.columns);
this.make_data(res.result, res.columns);
this.filter_hidden_columns();
this.render(res);
},
render: function(res) {
this.columnFilters = {};
this.make_dataview();
this.id = frappe.dom.set_unique_id(this.wrapper.find(".result-area").addClass("slick-wrapper").get(0));
this.grid = new Slick.Grid("#"+this.id, this.dataView, this.columns,
this.slickgrid_options);
if (!frappe.dom.is_touchscreen()) {
this.grid.setSelectionModel(new Slick.CellSelectionModel());
this.grid.registerPlugin(new Slick.CellExternalCopyManager({
dataItemColumnValueExtractor: function(item, columnDef, value) {
return item[columnDef.field];
}
}));
}
this.setup_header_row();
this.grid.init();
this.setup_sort();
// further setup of grid like click subscription for tree
if (this.get_query_report_opts().tree) {
this.setup_tree();
}
this.set_message(res.message);
this.setup_chart(res);
this.set_print_data(res.data_to_be_printed);
this.toggle_expand_collapse_buttons(this.is_tree_report);
},
make_columns: function(columns) {
var me = this;
var formatter = this.get_formatter();
this.columns = [{id: "_id", field: "_id", name: __("Sr No"), width: 60}]
.concat($.map(columns, function(c, i) {
if ($.isPlainObject(c)) {
var df = c;
} else if (c.indexOf(":")!==-1) {
var opts = c.split(":");
var df = {
label: opts.length<=2 ? opts[0] : opts.slice(0, opts.length - 2).join(":"),
fieldtype: opts.length<=2 ? opts[1] : opts[opts.length - 2],
width: opts.length<=2 ? opts[2] : opts[opts.length - 1]
};
if (df.fieldtype.indexOf("/")!==-1) {
var tmp = df.fieldtype.split("/");
df.fieldtype = tmp[0];
df.options = tmp[1];
}
df.width = cint(df.width);
} else {
var df = {
label: c,
fieldtype: "Data"
};
}
if (!df.fieldtype) df.fieldtype = "Data";
if (!cint(df.width)) df.width = 80;
var col = $.extend({}, df, {
label: df.label || (df.fieldname && __(toTitle(df.fieldname.replace(/_/g, " ")))) || "",
sortable: true,
df: df,
formatter: formatter
});
col.field = df.fieldname || df.label;
df.label = __(df.label);
col.name = col.id = col.label = df.label;
if(df.width < 0) {
col.hidden = true;
}
return col
}));
},
filter_hidden_columns: function() {
this.columns = $.map(this.columns, function(c, i) {
return (c.hidden==1 ? null : c);
});
},
get_query_report_opts: function() {
return frappe.query_reports[this.report_name] || {};
},
get_formatter: function() {
var formatter = function(row, cell, value, columnDef, dataContext, for_print) {
var value = frappe.format(value, columnDef.df, {for_print: for_print, always_show_decimals: true}, dataContext);
if (columnDef.df.is_tree) {
value = frappe.query_report.tree_formatter(row, cell, value, columnDef, dataContext);
}
return value;
};
var query_report_opts = this.get_query_report_opts();
if (query_report_opts.formatter) {
var default_formatter = formatter;
// custom formatter
formatter = function(row, cell, value, columnDef, dataContext) {
return query_report_opts.formatter(row, cell, value, columnDef, dataContext, default_formatter);
}
}
return formatter;
},
make_data: function(result, columns) {
var me = this;
this.data = [];
for(var row_idx=0, l=result.length; row_idx < l; row_idx++) {
var row = result[row_idx];
if ($.isPlainObject(row)) {
var newrow = row;
} else {
var newrow = {};
for(var i=1, j=this.columns.length; i<j; i++) {
newrow[this.columns[i].field] = row[i-1];
}
}
newrow._id = row_idx + 1;
newrow.id = newrow.name ? newrow.name : ("_" + newrow._id);
this.data.push(newrow);
}
if(this.data.length && this.report_doc.add_total_row) {
this.total_row_id = this.data[this.data.length - 1].id;
}
},
make_dataview: function() {
// initialize the model
this.dataView = new Slick.Data.DataView({ inlineFilters: true });
this.dataView.beginUpdate();
if (this.get_query_report_opts().tree) {
this.setup_item_by_name();
this.dataView.setFilter(this.tree_filter);
} else {
this.dataView.setFilter(this.inline_filter);
}
this.dataView.setItems(this.data);
this.dataView.endUpdate();
var me = this;
this.dataView.onRowCountChanged.subscribe(function (e, args) {
me.update_totals_row();
me.grid.updateRowCount();
me.grid.render();
});
this.dataView.onRowsChanged.subscribe(function (e, args) {
me.grid.invalidateRows(args.rows);
me.grid.render();
});
},
update_totals_row: function() {
if(!this.report_doc.add_total_row) return;
const number_fields = ['Currency', 'Float', 'Int'];
const fields = this.columns
.filter(col => number_fields.includes(col.fieldtype))
.map(col => col.field);
// reset numeric fields
let updated_totals = Object.assign({}, this.dataView.getItemById(this.total_row_id));
fields.map(field => {
updated_totals[field] = 0.0;
});
const data_length = this.dataView.getLength();
// loop all the rows except the last Total row
for (let i = 0; i < data_length - 1; i++) {
const item = this.dataView.getItem(i);
fields.map(field => {
updated_totals[field] += item[field];
});
}
this.dataView.updateItem(updated_totals.id, updated_totals);
},
inline_filter: function (item) {
var me = frappe.container.page.query_report;
if(me.report_doc.add_total_row) {
// always show totals row
if(item.id === me.total_row_id) return true;
}
for (var columnId in me.columnFilters) {
if (columnId !== undefined && me.columnFilters[columnId] !== "") {
var c = me.grid.getColumns()[me.grid.getColumnIndex(columnId)];
if (!me.compare_values(item[c.field], me.columnFilters[columnId],
me.columns[me.grid.getColumnIndex(columnId)])) {
return false;
}
}
}
return true;
},
setup_item_by_name: function() {
this.item_by_name = {};
this.name_field = this.get_query_report_opts().name_field;
this.parent_field = this.get_query_report_opts().parent_field;
var initial_depth = this.get_query_report_opts().initial_depth;
for (var i=0, l=this.data.length; i<l; i++) {
var item = this.data[i];
// only if name field has value
if (item[this.name_field]) {
this.item_by_name[item[this.name_field]] = item;
}
// set collapsed if initial depth is specified
if (initial_depth && item.indent && item.indent>=(initial_depth - 1)) {
item._collapsed = true;
}
}
},
toggle_all: function(collapse) {
var me = this;
for(var i=0, l=this.data.length; i<l; i++) {
var item = this.data[i];
item._collapsed = collapse;
me.dataView.updateItem(item.id, item);
}
},
tree_filter: function(item) {
var me = frappe.query_report;
// apply inline filters
if (!me.inline_filter(item)) return false;
try {
var parent_name = item[me.parent_field];
while (parent_name) {
if (!me.item_by_name[parent_name] || me.item_by_name[parent_name]._collapsed) {
return false;
}
parent_name = me.item_by_name[parent_name][me.parent_field];
}
return true;
} catch (e) {
if (e.message.indexOf("[parent_name] is undefined")!==-1) {
frappe.msgprint(__("Unable to display this tree report, due to missing data. Most likely, it is being filtered out due to permissions."));
}
throw e;
}
},
tree_formatter: function(row, cell, value, columnDef, dataContext) {
var me = frappe.query_report;
me.is_tree_report = true;
var $span = $("<span></span>")
.css("padding-left", (cint(dataContext.indent) * 21) + "px")
.html(value);
var idx = me.dataView.getIdxById(dataContext.id);
var show_toggle = me.data[idx + 1] && (me.data[idx + 1].indent > me.data[idx].indent)
if (dataContext[me.name_field] && show_toggle) {
$('<span class="toggle"></span>')
.addClass(dataContext._collapsed ? "expand" : "collapse")
.css("margin-right", "7px")
.prependTo($span);
}
return $span.wrap("<p></p>").parent().html();
},
compare_values: function(value, filter, columnDef) {
var invert = false;
// check if invert
if(filter[0]=="!") {
invert = true;
filter = filter.substr(1);
}
var out = false;
var cond = "=="
// parse condition
if(filter[0]==">") {
filter = filter.substr(1);
cond = ">"
} else if(filter[0]=="<") {
filter = filter.substr(1);
cond = "<"
}
if(in_list(['Float', 'Currency', 'Int', 'Date'], columnDef.df.fieldtype)) {
// non strings
if(filter.indexOf(":")==-1) {
if(columnDef.df.fieldtype=="Date") {
filter = frappe.datetime.user_to_str(filter);
}
if(in_list(["Float", "Currency", "Int"], columnDef.df.fieldtype)) {
value = flt(value);
filter = flt(filter);
}
out = eval("value" + cond + "filter");
} else {
// range
filter = filter.split(":");
if(columnDef.df.fieldtype=="Date") {
filter[0] = frappe.datetime.user_to_str(filter[0]);
filter[1] = frappe.datetime.user_to_str(filter[1]);
}
if(in_list(["Float", "Currency", "Int"], columnDef.df.fieldtype)) {
value = flt(value);
filter[0] = flt(filter[0]);
filter[1] = flt(filter[1]);
}
out = value >= filter[0] && value <= filter[1];
}
} else {
// string
value = value + "";
value = value.toLowerCase();
filter = filter.toLowerCase();
out = value.indexOf(filter) != -1;
}
if(invert)
return !out;
else
return out;
},
setup_header_row: function() {
var me = this;
$(this.grid.getHeaderRow()).delegate(":input", "change keyup", function (e) {
var columnId = $(this).data("columnId");
if (columnId != null) {
me.columnFilters[columnId] = $.trim($(this).val());
me.dataView.refresh();
}
});
this.grid.onHeaderRowCellRendered.subscribe(function(e, args) {
$(args.node).empty();
$("<input type='text'>")
.data("columnId", args.column.id)
.val(me.columnFilters[args.column.id])
.appendTo(args.node);
});
},
setup_sort: function() {
var me = this;
this.grid.onSort.subscribe(function (e, args) {
var cols = args.sortCols;
me.data.sort(function (dataRow1, dataRow2) {
// Totals row should always be last
if(me.report_doc.add_total_row) {
if(dataRow1.id === me.total_row_id) {
return 1;
}
if(dataRow2.id === me.total_row_id) {
return -1;
}
}
for (var i = 0, l = cols.length; i < l; i++) {
var field = cols[i].sortCol.field;
var sign = cols[i].sortAsc ? 1 : -1;
var value1 = dataRow1[field], value2 = dataRow2[field];
var result = (value1 == value2 ? 0 : (value1 > value2 ? 1 : -1)) * sign;
if (result != 0) {
return result;
}
}
return 0;
});
me.dataView.beginUpdate();
me.dataView.setItems(me.data);
me.dataView.endUpdate();
me.dataView.refresh();
});
},
setup_tree: function() {
// set these in frappe.query_reports[report_name]
// "tree": true,
// "name_field": "account",
// "parent_field": "parent_account",
// "initial_depth": 3
// also set "is_tree" true for ColumnDef
var me = this;
this.grid.onClick.subscribe(function (e, args) {
if ($(e.target).hasClass("toggle")) {
var item = me.dataView.getItem(args.row);
if (item) {
if (!item._collapsed) {
item._collapsed = true;
} else {
item._collapsed = false;
}
me.dataView.updateItem(item.id, item);
}
e.stopImmediatePropagation();
}
});
},
make_export: function() {
var me = this;
this.title = this.report_name;
if(!frappe.model.can_export(this.report_doc.ref_doctype)) {
frappe.msgprint(__("You are not allowed to export this report"));
return false;
}
frappe.prompt({fieldtype:"Select", label: __("Select File Type"), fieldname:"file_format_type",
options:"Excel\nCSV", default:"Excel", reqd: 1},
function(data) {
var view_data = frappe.slickgrid_tools.get_view_data(me.columns, me.dataView);
var result = view_data.map(row => row.splice(1));
// to download only visible rows
var visible_idx = view_data.map(row => row[0]).filter(sr_no => sr_no !== 'Sr No');
if (data.file_format_type == "CSV") {
frappe.tools.downloadify(result, null, me.title);
}
else if (data.file_format_type == "Excel") {
try {
var filters = me.get_values(true);
} catch(e) {
return;
}
var args = {
cmd: 'frappe.desk.query_report.export_query',
report_name: me.report_name,
file_format_type: data.file_format_type,
filters: filters,
visible_idx: visible_idx,
}
open_url_post(frappe.request.url, args);
}
}, __("Export Report: "+ me.title), __("Download"));
return false;
},
set_message: function(msg) {
if(msg) {
this.wrapper.find(".help-msg").html(msg).toggle(true);
} else {
this.wrapper.find(".help-msg").empty().toggle(false);
}
},
setup_chart: function(res) {
this.chart_area.toggle(false);
if (this.get_query_report_opts().get_chart_data) {
var opts = this.get_query_report_opts().get_chart_data(res.columns, res.result);
} else if (res.chart) {
var opts = res.chart;
} else {
return;
}
$.extend(opts, {
parent: ".chart-area",
height: 200
});
console.log(opts)
if(opts.data && opts.data.labels && opts.data.labels.length) {
this.chart_area.toggle(true);
this.chart = new Chart(opts);
}
},
set_print_data: function(data_to_be_printed) {
this.data_to_be_printed = data_to_be_printed;
}
})

File diff suppressed because one or more lines are too long

View file

@ -60,6 +60,15 @@
.data-table-row.row-highlight {
background-color: @extra-light-yellow;
}
.report-tree-node {
display: inline-block;
.toggle {
cursor: pointer;
font-size: @text-regular;
}
}
}
@keyframes breathe {

View file

@ -22,6 +22,7 @@
margin-right: 10px;
max-width: 50%;
vertical-align: middle;
overflow: visible;
}
@media(min-width: @screen-xs) {

View file

@ -612,9 +612,10 @@ forwarded@~0.1.2:
frappe-datatable@frappe/datatable:
version "0.0.2"
resolved "https://codeload.github.com/frappe/datatable/tar.gz/3c4198fc8354a018c98e37287b946da692a9265d"
resolved "https://codeload.github.com/frappe/datatable/tar.gz/5b8be4c5b09a243ed8580cb9667d7658ca249f02"
dependencies:
clusterize.js "^0.18.0"
lodash "^4.17.5"
sortablejs "^1.7.0"
fresh@0.5.2:
@ -986,6 +987,10 @@ locate-path@^2.0.0:
p-locate "^2.0.0"
path-exists "^3.0.0"
lodash@^4.17.5:
version "4.17.5"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
lru-cache@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"