Skip to content

Instantly share code, notes, and snippets.

@vijaywm
Last active January 15, 2021 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vijaywm/1423bc049c26adea1fc7aab3cec979aa to your computer and use it in GitHub Desktop.
Save vijaywm/1423bc049c26adea1fc7aab3cec979aa to your computer and use it in GitHub Desktop.
frappe.provide("frappe.views");
frappe.ui.form.on("Tab Report", {
onload: function (frm) {
if (!frm.is_new()) {
frm.tab_report = new frappe.views.TabReport({ frm: frm });
frm.tab_report.show();
}
},
refresh: function (frm) {
if (!frm.is_new()) {
frm.tab_report.load_report();
}
},
});
frappe.views.TabReport = class TabReport {
constructor(opts) {
Object.assign(this, opts);
}
show() {
frappe.run_serially([() => this.init()]);
}
init() {
if (this.init_promise) return this.init_promise;
let tasks = [this.setup_defaults].map((fn) => fn.bind(this));
this.init_promise = frappe.run_serially(tasks);
return this.init_promise;
}
setup_defaults() {
// set full-width
this.frm.$wrapper.find(".container.page-body").addClass("col-md-12");
$("<div id='tab-report' class='table-bordered'></div>").appendTo(
this.frm.fields_dict["report_html"].$wrapper
);
}
load_report() {
this.report_name = this.frm.doc.report_name;
frappe.run_serially([
() => this.get_report_doc(),
() => this.get_report_settings(),
() => this.refresh_report(),
]);
}
get_report_doc() {}
refresh_report() {
return frappe.run_serially([
() => this.setup_filters(),
// () => this.set_route_filters(),
// () => this.report_settings.onload && this.report_settings.onload(this),
// () => this.get_user_settings(),
() => this.refresh(),
]);
}
get_filter_values(raise) {
const mandatory = this.filters.filter((f) => f.df.reqd);
const missing_mandatory = mandatory.filter((f) => !f.get_value());
if (raise && missing_mandatory.length > 0) {
let message = __("Please set filters");
this.toggle_message(raise, message);
throw "Filter missing";
} else {
this.toggle_message(raise);
}
const filters = this.filters
.filter((f) => f.get_value())
.map((f) => {
var v = f.get_value();
// hidden fields dont have $input
if (f.df.hidden) v = f.value;
if (v === "%") v = null;
return {
[f.df.fieldname]: v,
};
})
.reduce((acc, f) => {
Object.assign(acc, f);
return acc;
}, {});
return filters;
}
setup_filters() {
const { filters = [] } = this.report_settings;
this.filters = filters
.map((df) => {
if (df.fieldtype === "Break") return;
let f = this.frm.page.add_field(df);
if (df.default) {
f.set_input(df.default);
}
if (df.get_query) f.get_query = df.get_query;
if (df.on_change) f.on_change = df.on_change;
df.onchange = () => {
let current_filters = this.get_filter_values();
if (
this.previous_filters &&
JSON.stringify(this.previous_filters) ===
JSON.stringify(current_filters)
) {
// filter values have not changed
return;
}
// clear previous_filters after 10 seconds, to allow refresh for new data
this.previous_filters = current_filters;
setTimeout(() => (this.previous_filters = null), 10000);
if (f.on_change) {
f.on_change(this);
} else {
if (!this._no_refresh) {
this.refresh();
}
}
};
f = Object.assign(f, df);
return f;
})
.filter(Boolean);
if (this.filters.length === 0) {
// hide page form if no filters
this.frm.page.hide_form();
} else {
this.frm.page.show_form();
}
}
refresh() {
let filters = this.get_filter_values(true);
// only one refresh at a time
if (this.last_ajax) {
this.last_ajax.abort();
}
return new Promise((resolve) => {
this.last_ajax = frappe.call({
method: "frappe.desk.query_report.run",
type: "GET",
args: {
report_name: this.report_name,
filters: filters,
},
callback: resolve,
always: () => this.frm.page.btn_secondary.prop("disabled", false),
});
}).then((r) => {
let data = r.message;
this.execution_time = data.execution_time || 0.1;
this.prepare_report_data(data);
this.render_datatable();
});
}
prepare_report_data(data) {
this.data = data;
// prepare columns
this.data.columns.forEach((t) => {
t.title = t.label;
t.field = t.fieldname;
});
// set column cell formatter for known fieldtypes, if not provided already in report_settings
// list of Tabulator formatters: http://tabulator.info/docs/4.9/format
this.data.columns.forEach((col) => {
if (!col.formatter) {
this.set_column_formatter(col);
}
});
}
set_column_formatter(col) {
if (!col.fieldtype || col.fieldtype == "") {
if (
this.data &&
this.data.result &&
this.data.result.length &&
is_html(this.data.result[0][col.fieldname])
)
col.formatter = "html";
} else if (col.fieldtype == "Link") {
col.formatter = function (cell) {
return `<a href='desk#Form/${
col.options
}/${cell.getValue()}'>${cell.getValue()}</a>`;
};
}
}
render_datatable() {
if (!this.tabulator) {
let opts = this.report_settings.opts || {};
Object.assign(opts, {
height: `${this.frm.doc.height || 400}px`,
placeholder: "No data to display",
});
this.tabulator = new Tabulator("#tab-report", opts);
}
this.tabulator.setColumns(this.data.columns);
this.tabulator.setData(this.data.result || []);
}
get_report_settings() {
return new Promise((resolve, reject) => {
if (frappe.query_reports[this.frm.doc.report_name]) {
this.report_settings = frappe.query_reports[this.frm.doc.report_name];
resolve();
} else {
frappe
.xcall("frappe.desk.query_report.get_script", {
report_name: this.frm.doc.report_name,
})
.then((settings) => {
frappe.dom.eval(settings.script || "");
frappe.after_ajax(() => {
this.report_settings = this.get_local_report_settings();
this.report_settings.html_format = settings.html_format;
this.report_settings.execution_time =
settings.execution_time || 0;
frappe.query_reports[this.report_name] = this.report_settings;
resolve();
});
})
.catch(reject);
}
});
}
get_local_report_settings() {
return frappe.query_reports[this.report_name] || {};
}
toggle_message(flag, message) {
if (!message) this.frm.layout.show_message();
else this.frm.layout.show_message(message);
}
};
// TODO: move to utils
function is_html(str) {
return basic_html.test(str) || full_html.test(str);
}
const HTML_TAGS = [
"a",
"abbr",
"address",
"area",
"article",
"aside",
"audio",
"b",
"base",
"bdi",
"bdo",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"cite",
"code",
"col",
"colgroup",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hr",
"html",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"label",
"legend",
"li",
"link",
"main",
"map",
"mark",
"math",
"menu",
"menuitem",
"meta",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"pre",
"progress",
"q",
"rb",
"rp",
"rt",
"rtc",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"slot",
"small",
"source",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"svg",
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"u",
"ul",
"var",
"video",
"wbr",
];
const basic_html = /\s?<!doctype html>|(<html\b[^>]*>|<body\b[^>]*>|<x-[^>]+>)+/i;
const full_html = new RegExp(
HTML_TAGS.map((tag) => `<${tag}\\b[^>]*>`).join("|"),
"i"
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment