diff --git a/frappe/__init__.py b/frappe/__init__.py index 6544206979..2fae079cde 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template -__version__ = '10.0.15' +__version__ = '10.0.16' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/public/js/frappe/list/list_renderer.js b/frappe/public/js/frappe/list/list_renderer.js index bfb3054fae..c298420665 100644 --- a/frappe/public/js/frappe/list/list_renderer.js +++ b/frappe/public/js/frappe/list/list_renderer.js @@ -3,9 +3,10 @@ frappe.provide('frappe.views'); -// Renders customized list -// usually based on `in_list_view` property - +/** +* Renders customized list. Usually based on `in_list_view` property. +* It carries information that is used in frappe.views.ListView +*/ frappe.views.ListRenderer = Class.extend({ name: 'List', init: function (opts) { @@ -38,6 +39,7 @@ frappe.views.ListRenderer = Class.extend({ // default settings this.order_by = this.order_by || 'modified desc'; + this.group_by = this.group_by || ''; this.filters = this.filters || []; this.or_filters = this.or_filters || []; this.page_length = this.page_length || 20; @@ -54,6 +56,7 @@ frappe.views.ListRenderer = Class.extend({ this.init_user_settings(); this.order_by = this.user_settings.order_by || this.settings.order_by; + this.group_by = this.get_group_by(); this.filters = this.user_settings.filters || this.settings.filters; this.page_length = this.settings.page_length; @@ -62,6 +65,16 @@ frappe.views.ListRenderer = Class.extend({ this.filters = [[this.doctype, "docstatus", "!=", 2]]; } }, + + /** + * Get the name of the column to use in SQL `group by`. + * It defaults to 'creation' + */ + get_group_by: function() { + const default_column = this.settings.group_by || 'creation'; + const group_by = $.format('`tab{0}`.`{1}`', [this.doctype, default_column]); + return group_by; + }, init_user_settings: function () { frappe.provide('frappe.model.user_settings.' + this.doctype + '.' + this.name); this.user_settings = frappe.get_user_settings(this.doctype)[this.name]; diff --git a/frappe/public/js/frappe/ui/base_list.js b/frappe/public/js/frappe/ui/base_list.js new file mode 100644 index 0000000000..494e2d6656 --- /dev/null +++ b/frappe/public/js/frappe/ui/base_list.js @@ -0,0 +1,540 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +// new re-re-factored Listing object +// now called BaseList +// +// opts: +// parent + +// method (method to call on server) +// args (additional args to method) +// get_args (method to return args as dict) + +// show_filters [false] +// doctype +// filter_fields (if given, this list is rendered, else built from doctype) + +// query or get_query (will be deprecated) +// query_max +// buttons_in_frame + +// no_result_message ("No result") + +// page_length (20) +// hide_refresh (False) +// no_toolbar +// new_doctype +// [function] render_row(parent, data) +// [function] onrun +// no_loading (no ajax indicator) + +frappe.provide('frappe.ui'); + +frappe.ui.BaseList = Class.extend({ + init: function (opts) { + this.opts = opts || {}; + this.set_defaults(); + if (opts) { + this.make(); + } + }, + set_defaults: function () { + this.page_length = 20; + this.start = 0; + this.data = []; + }, + make: function (opts) { + if (opts) { + this.opts = opts; + } + this.prepare_opts(); + + $.extend(this, this.opts); + + // make dom + this.wrapper = $(frappe.render_template('listing', this.opts)); + this.parent.append(this.wrapper); + + this.set_events(); + + if (this.page) { + this.wrapper.find('.list-toolbar-wrapper').hide(); + } + + if (this.show_filters) { + this.make_filters(); + } + }, + prepare_opts: function () { + if (this.opts.new_doctype) { + if (!frappe.boot.user.can_create.includes(this.opts.new_doctype)) { + this.opts.new_doctype = null; + } + } + if (!this.opts.no_result_message) { + this.opts.no_result_message = __('Nothing to show'); + } + if (!this.opts.page_length) { + this.opts.page_length = this.user_settings && this.user_settings.limit || 20; + } + this.opts._more = __('More'); + }, + add_button: function (label, click, icon) { + if (this.page) { + return this.page.add_menu_item(label, click, icon) + } else { + this.wrapper.find('.list-toolbar-wrapper').removeClass('hide'); + return $('') + .appendTo(this.wrapper.find('.list-toolbar')) + .html((icon ? (' ') : '') + label) + .click(click); + } + }, + set_events: function () { + var me = this; + + // next page + this.wrapper.find('.btn-more').click(function () { + me.run(true); + }); + + this.wrapper.find(".btn-group-paging").on('click', '.btn', function () { + me.page_length = cint($(this).attr("data-value")); + + me.wrapper.find(".btn-group-paging .btn-info").removeClass("btn-info"); + $(this).addClass("btn-info"); + + // always reset when changing list page length + me.run(); + }); + + // select the correct page length + if (this.opts.page_length !== 20) { + this.wrapper.find(".btn-group-paging .btn-info").removeClass("btn-info"); + this.wrapper + .find(".btn-group-paging .btn[data-value='" + this.opts.page_length + "']") + .addClass('btn-info'); + } + + // title + if (this.title) { + this.wrapper.find('h3').html(this.title).show(); + } + + // new + this.set_primary_action(); + + if (me.no_toolbar || me.hide_toolbar) { + me.wrapper.find('.list-toolbar-wrapper').hide(); + } + }, + + set_primary_action: function () { + var me = this; + if (this.new_doctype) { + this.page.set_primary_action( + __("New"), + me.make_new_doc.bind(me, me.new_doctype), + "octicon octicon-plus" + ); + } else { + this.page.clear_primary_action(); + } + }, + + make_new_doc: function (doctype) { + var me = this; + frappe.model.with_doctype(doctype, function () { + if (me.custom_new_doc) { + me.custom_new_doc(doctype); + } else { + if (me.filter_list) { + frappe.route_options = {}; + me.filter_list.get_filters().forEach(function (f, i) { + if (f[2] === "=" && !frappe.model.std_fields_list.includes(f[1])) { + frappe.route_options[f[1]] = f[3]; + } + }); + } + frappe.new_doc(doctype, true); + } + }); + }, + + make_filters: function () { + this.make_standard_filters(); + + this.filter_list = new frappe.ui.FilterList({ + base_list: this, + parent: this.wrapper.find('.list-filters').show(), + doctype: this.doctype, + filter_fields: this.filter_fields, + default_filters: this.default_filters || [] + }); + // default filter for submittable doctype + if (frappe.model.is_submittable(this.doctype)) { + this.filter_list.add_filter(this.doctype, "docstatus", "!=", 2); + } + }, + + make_standard_filters: function() { + var me = this; + if (this.standard_filters_added) { + return; + } + + if (this.meta) { + var filter_count = 1; + if(this.is_list_view) { + $(``) + .prependTo(this.page.page_form); + } + this.page.add_field({ + fieldtype: 'Data', + label: 'ID', + condition: 'like', + fieldname: 'name', + onchange: () => { me.refresh(true); } + }); + + this.meta.fields.forEach(function(df, i) { + if(df.in_standard_filter && !frappe.model.no_value_type.includes(df.fieldtype)) { + let options = df.options; + let condition = '='; + let fieldtype = df.fieldtype; + if (['Text', 'Small Text', 'Text Editor', 'Data'].includes(fieldtype)) { + fieldtype = 'Data'; + condition = 'like'; + } + if(df.fieldtype == "Select" && df.options) { + options = df.options.split("\n"); + if(options.length > 0 && options[0] != "") { + options.unshift(""); + options = options.join("\n"); + } + } + let f = me.page.add_field({ + fieldtype: fieldtype, + label: __(df.label), + options: options, + fieldname: df.fieldname, + condition: condition, + onchange: () => {me.refresh(true);} + }); + filter_count ++; + if (filter_count > 3) { + $(f.wrapper).addClass('hidden-sm').addClass('hidden-xs'); + } + if (filter_count > 5) { + return false; + } + } + }); + } + + this.standard_filters_added = true; + }, + + update_standard_filters: function(filters) { + let me = this; + for(let key in this.page.fields_dict) { + let field = this.page.fields_dict[key]; + let value = field.get_value(); + if (value) { + if (field.df.condition==='like' && !value.includes('%')) { + value = '%' + value + '%'; + } + filters.push([ + me.doctype, + field.df.fieldname, + field.df.condition || '=', + value + ]); + } + } + }, + + + clear: function () { + this.data = []; + this.wrapper.find('.result-list').empty(); + this.wrapper.find('.result').show(); + this.wrapper.find('.no-result').hide(); + this.start = 0; + this.onreset && this.onreset(); + }, + + /* + * Uses the value of `frappe.route_options` to automatically set + * a filter in a list view. + */ + set_filters_from_route_options: function ({clear_filters=true} = {}) { + var me = this; + if(this.filter_list && clear_filters) { + this.filter_list.clear_filters(); + } + + for(var field in frappe.route_options) { + var value = frappe.route_options[field]; + var doctype = null; + + // if `Child DocType.fieldname` + if (field.includes(".")) { + doctype = field.split(".")[0]; + field = field.split(".")[1]; + } + + // find the table in which the key exists + // for example the filter could be {"item_code": "X"} + // where item_code is in the child table. + + // we can search all tables for mapping the doctype + if (!doctype) { + doctype = frappe.meta.get_doctype_for_field(me.doctype, field); + } + + if (doctype && me.filter_list) { + if ($.isArray(value)) { + me.filter_list.add_filter(doctype, field, value[0], value[1]); + } else { + me.filter_list.add_filter(doctype, field, "=", value); + } + } + } + frappe.route_options = null; + }, + + run: function(more) { + setTimeout(() => this._run(more), 100); + }, + + _run: function (more) { + var me = this; + if (!more) { + this.start = 0; + this.onreset && this.onreset(); + } + + var args = this.get_call_args(); + this.save_user_settings_locally(args); + + // user_settings are saved by db_query.py when dirty + $.extend(args, { + user_settings: frappe.model.user_settings[this.doctype] + }); + + return frappe.call({ + method: this.opts.method || 'frappe.desk.query_builder.runquery', + type: "GET", + freeze: this.opts.freeze !== undefined ? this.opts.freeze : true, + args: args, + callback: function (r) { + me.dirty = false; + me.render_results(r); + }, + no_spinner: this.opts.no_loading + }); + }, + save_user_settings_locally: function (args) { + if (this.opts.save_user_settings && this.doctype && !this.docname) { + // save list settings locally + var user_settings = frappe.model.user_settings[this.doctype]; + var different = false; + + if (!user_settings) { + return; + } + + if (!frappe.utils.arrays_equal(args.filters, user_settings.filters)) { + // settings are dirty if filters change + user_settings.filters = args.filters; + different = true; + } + + if (user_settings.order_by !== args.order_by) { + user_settings.order_by = args.order_by; + different = true; + } + + if (user_settings.limit !== args.limit_page_length) { + user_settings.limit = args.limit_page_length || 20 + different = true; + } + + // save fields in list settings + if (args.save_user_settings_fields) { + user_settings.fields = args.fields; + } + + if (different) { + user_settings.updated_on = moment().toString(); + } + } + }, + + /* + * Prepares arguments that will be used to query the database to + * return the desired records for the list view + */ + get_call_args: function () { + if (!this.method) { + var query = this.get_query && this.get_query() || this.query; + query = this.add_limits(query); + var args = { + query_max: this.query_max, + as_dict: 1 + } + args.simple_query = query; + } else { + var args = { + start: this.start, + page_length: this.page_length + } + } + + // append user-defined arguments + if (this.args) + $.extend(args, this.args) + + if (this.get_args) { + $.extend(args, this.get_args()); + } + return args; + }, + render_results: function (r) { + if (this.start === 0) + this.clear(); + + this.wrapper.find('.btn-more, .list-loading').hide(); + + var values = []; + + if (r.message) { + values = this.get_values_from_response(r.message); + } + + var show_results = true; + if(this.show_no_result) { + if($.isFunction(this.show_no_result)) { + show_results = !this.show_no_result() + } else { + show_results = !this.show_no_result; + } + } + + // render result view when + // length > 0 OR + // explicitly set by flag + if (values.length || show_results) { + this.data = this.data.concat(values); + this.render_view(values); + this.update_paging(values); + } else if (this.start === 0) { + // show no result message + this.wrapper.find('.result').hide(); + + var msg = ''; + var no_result_message = this.no_result_message; + if(no_result_message && $.isFunction(no_result_message)) { + msg = no_result_message(); + } else if(typeof no_result_message === 'string') { + msg = no_result_message; + } else { + msg = __('No Results') + } + + this.wrapper.find('.no-result').html(msg).show(); + } + + this.wrapper.find('.list-paging-area') + .toggle(values.length > 0|| this.start > 0); + + // callbacks + if (this.onrun) this.onrun(); + if (this.callback) this.callback(r); + this.wrapper.trigger("render-complete"); + }, + + get_values_from_response: function (data) { + // make dictionaries from keys and values + if (data.keys && $.isArray(data.keys)) { + return frappe.utils.dict(data.keys, data.values); + } else { + return data; + } + }, + + render_view: function (values) { + // override this method in derived class + }, + + update_paging: function (values) { + if (values.length >= this.page_length) { + this.wrapper.find('.btn-more').show(); + this.start += this.page_length; + } + }, + + refresh: function () { + this.run(); + }, + add_limits: function (query) { + return query + ' LIMIT ' + this.start + ',' + (this.page_length + 1); + }, + set_filter: function (fieldname, label, no_run, no_duplicate) { + var filter = this.filter_list.get_filter(fieldname); + if (filter) { + var value = cstr(filter.field.get_value()); + if (value.includes(label)) { + // already set + return false + + } else if (no_duplicate) { + filter.set_values(this.doctype, fieldname, "=", label); + } else { + // second filter set for this field + if (fieldname == '_user_tags' || fieldname == "_liked_by") { + // and for tags + this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%'); + } else { + // or for rest using "in" + filter.set_values(this.doctype, fieldname, 'in', value + ', ' + label); + } + } + } else { + // no filter for this item, + // setup one + if (['_user_tags', '_comments', '_assign', '_liked_by'].includes(fieldname)) { + this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%'); + } else { + this.filter_list.add_filter(this.doctype, fieldname, '=', label); + } + } + if (!no_run) + this.run(); + }, + init_user_settings: function () { + this.user_settings = frappe.model.user_settings[this.doctype] || {}; + }, + call_for_selected_items: function (method, args) { + var me = this; + args.names = this.get_checked_items().map(function (item) { + return item.name; + }); + + frappe.call({ + method: method, + args: args, + freeze: true, + callback: function (r) { + if (!r.exc) { + if (me.list_header) { + me.list_header.find(".list-select-all").prop("checked", false); + } + me.refresh(true); + } + } + }); + } +}); diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js new file mode 100644 index 0000000000..ce513bd73e --- /dev/null +++ b/frappe/public/js/frappe/ui/filters/filters.js @@ -0,0 +1,691 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.ui.FilterList = Class.extend({ + init: function(opts) { + $.extend(this, opts); + this.filters = []; + this.wrapper = this.parent; + this.stats = []; + this.make(); + this.set_events(); + }, + make: function() { + this.wrapper.find('.show_filters, .filter_area').remove(); + this.wrapper.append(` +
+
+ +
+
+
`); + }, + set_events: function() { + var me = this; + // show filters + this.wrapper.find('.new-filter').bind('click', function() { + me.add_filter(); + }); + + this.wrapper.find('.clear-filters').bind('click', function() { + me.clear_filters(); + $('.date-range-picker').val('') + me.base_list.run(); + $(this).addClass("hide"); + }); + }, + + show_filters: function() { + this.wrapper.find('.show_filters').toggle(); + if(!this.filters.length) { + this.add_filter(this.doctype, 'name'); + this.filters[0].wrapper.find(".filter_field input").focus(); + } + }, + + /* + * Removes all filters. + */ + clear_filters: function() { + $.each(this.filters, function(i, f) { f.remove(true); }); + if(this.base_list.page.fields_dict) { + $.each(this.base_list.page.fields_dict, (key, value) => { + value.set_input(''); + }); + } + this.filters = []; + }, + + /* + * Adds a new filter. + * @param {string} doctype + * @param {string} fieldname + * @param {string} condition + * @param {string} value + * @param {string} hidden + * @returns {Boolean} - Returns true if filter is added + */ + add_filter: function(doctype, fieldname, condition, value, hidden) { + // adds a new filter, returns true if filter has been added + + // allow equal to be used as like + let base_filter = this.base_list.page.fields_dict[fieldname]; + if (base_filter + && (base_filter.df.condition==condition + || (condition==='=' && base_filter.df.condition==='like'))) { + // if filter exists in base_list, then exit + this.base_list.page.fields_dict[fieldname].set_input(value); + + return true; + } + + if(doctype && fieldname + && !frappe.meta.has_field(doctype, fieldname) + && !in_list(frappe.model.std_fields_list, fieldname)) { + frappe.msgprint({ + message: __('Filter {0} missing', [fieldname.bold()]), + title: 'Invalid Filter', + indicator: 'red' + }); + return false; + } + + this.wrapper.find('.show_filters').toggle(true); + var is_new_filter = arguments.length===0; + + if (is_new_filter && this.wrapper.find(".is-new-filter:visible").length) { + // only allow 1 new filter at a time! + return false; + } + + var filter = this.push_new_filter(doctype, fieldname, condition, value); + if (!filter) return; + + if(this.wrapper.find('.clear-filters').hasClass("hide")) { + this.wrapper.find('.clear-filters').removeClass("hide"); + } + + if (filter && is_new_filter) { + filter.wrapper.addClass("is-new-filter"); + } else { + filter.freeze(); + } + + if (hidden) { + filter.$btn_group.addClass("hide"); + } + + return true; + }, + push_new_filter: function(doctype, fieldname, condition, value) { + if(this.filter_exists(doctype, fieldname, condition, value)) { + return; + } + + // if standard filter exists, then clear it. + if(this.base_list.page.fields_dict[fieldname]) { + this.base_list.page.fields_dict[fieldname].set_input(''); + } + + var filter = new frappe.ui.Filter({ + flist: this, + _doctype: doctype, + fieldname: fieldname, + condition: condition, + value: value + }); + + this.filters.push(filter); + + return filter; + }, + + remove: function(filter) { + // remove `filter` from flist + for (var i in this.filters) { + if (this.filters[i] === filter) { + break; + } + } + if (i!==undefined) { + // remove index + this.filters.splice(i, 1); + } + }, + + filter_exists: function(doctype, fieldname, condition, value) { + var flag = false; + for(var i in this.filters) { + if(this.filters[i].field) { + var f = this.filters[i].get_value(); + + if(f[0]==doctype && f[1]==fieldname && f[2]==condition && f[3]==value) { + flag = true; + } else if($.isArray(value) && frappe.utils.arrays_equal(value, f[3])) { + flag = true; + } + } + } + return flag; + }, + + get_filters: function() { + // get filter values as dict + var values = []; + $.each(this.filters, function(i, filter) { + if(filter.field) { + filter.freeze(); + values.push(filter.get_value()); + } + }); + this.base_list.update_standard_filters(values); + + return values; + }, + + // remove hidden filters + update_filters: function() { + var fl = []; + $.each(this.filters, function(i, f) { + if(f.field) fl.push(f); + }) + this.filters = fl; + if(this.filters.length === 0) { + this.wrapper.find('.clear-filters').addClass("hide"); + } + }, + + get_filter: function(fieldname) { + for(var i in this.filters) { + if(this.filters[i].field && this.filters[i].field.df.fieldname==fieldname) + return this.filters[i]; + } + }, + + get_formatted_value: function(field, val){ + var value = val; + + if(field.df.fieldname==="docstatus") { + value = {0:"Draft", 1:"Submitted", 2:"Cancelled"}[value] || value; + } else if(field.df.original_type==="Check") { + value = {0:"No", 1:"Yes"}[cint(value)]; + } + + value = frappe.format(value, field.df, {only_value: 1}); + return value; + } +}); + +frappe.ui.Filter = Class.extend({ + init: function(opts) { + $.extend(this, opts); + + this.doctype = this.flist.doctype; + this.make(); + this.make_select(); + this.set_events(); + }, + make: function() { + this.wrapper = $(frappe.render_template("edit_filter", {})) + .appendTo(this.flist.wrapper.find('.filter_area')); + }, + make_select: function() { + var me = this; + this.fieldselect = new frappe.ui.FieldSelect({ + parent: this.wrapper.find('.fieldname_select_area'), + doctype: this.doctype, + filter_fields: this.filter_fields, + select: function(doctype, fieldname) { + me.set_field(doctype, fieldname); + } + }); + if(this.fieldname) { + this.fieldselect.set_value(this._doctype || this.doctype, this.fieldname); + } + }, + set_events: function() { + var me = this; + + this.wrapper.find("a.remove-filter").on("click", function() { + me.remove(); + }); + + this.wrapper.find(".set-filter-and-run").on("click", function() { + me.wrapper.removeClass("is-new-filter"); + me.flist.base_list.run(); + me.apply(); + }); + + // add help for "in" codition + me.wrapper.find('.condition').change(function() { + if(!me.field) return; + var condition = $(this).val(); + if(in_list(["in", "like", "not in", "not like"], condition)) { + me.set_field(me.field.df.parent, me.field.df.fieldname, 'Data', condition); + if(!me.field.desc_area) { + me.field.desc_area = $('
').appendTo(me.field.wrapper); + } + // set description + me.field.desc_area.html((in_list(["in", "not in"], condition)==="in" + ? __("values separated by commas") + : __("use % as wildcard"))+'
'); + } else { + //if condition selected after refresh + me.set_field(me.field.df.parent, me.field.df.fieldname, null, condition); + } + }); + + // set the field + if(me.fieldname) { + // pre-sets given (could be via tags!) + return this.set_values(me._doctype, me.fieldname, me.condition, me.value); + } else { + me.set_field(me.doctype, 'name'); + } + }, + + apply: function() { + var f = this.get_value(); + + this.flist.remove(this); + this.flist.push_new_filter(f[0], f[1], f[2], f[3]); + this.wrapper.remove(); + this.flist.update_filters(); + }, + + remove: function(dont_run) { + this.wrapper.remove(); + this.$btn_group && this.$btn_group.remove(); + this.field = null; + this.flist.update_filters(); + + if(!dont_run) { + this.flist.base_list.refresh(true); + } + }, + + set_values: function(doctype, fieldname, condition, value) { + // presents given (could be via tags!) + this.set_field(doctype, fieldname); + + // change 0,1 to Yes, No for check field type + if(this.field.df.original_type==='Check') { + if(value==0) value = 'No'; + else if(value==1) value = 'Yes'; + } + + if(condition) { + this.wrapper.find('.condition').val(condition).change(); + } + if(value!=null) { + return this.field.set_value(value); + } + }, + + set_field: function(doctype, fieldname, fieldtype, condition) { + var me = this; + + // set in fieldname (again) + var cur = me.field ? { + fieldname: me.field.df.fieldname, + fieldtype: me.field.df.fieldtype, + parent: me.field.df.parent, + } : {}; + + var original_docfield = me.fieldselect.fields_by_name[doctype][fieldname]; + if(!original_docfield) { + frappe.msgprint(__("Field {0} is not selectable.", [fieldname])); + return; + } + + var df = copy_dict(me.fieldselect.fields_by_name[doctype][fieldname]); + + // filter field shouldn't be read only or hidden + df.read_only = 0; + df.hidden = 0; + + if(!condition) this.set_default_condition(df, fieldtype); + this.set_fieldtype(df, fieldtype); + + // called when condition is changed, + // don't change if all is well + if(me.field && cur.fieldname == fieldname && df.fieldtype == cur.fieldtype && + df.parent == cur.parent) { + return; + } + + // clear field area and make field + me.fieldselect.selected_doctype = doctype; + me.fieldselect.selected_fieldname = fieldname; + + // save old text + var old_text = null; + if(me.field) { + old_text = me.field.get_value(); + } + + var field_area = me.wrapper.find('.filter_field').empty().get(0); + var f = frappe.ui.form.make_control({ + df: df, + parent: field_area, + only_input: true, + }) + f.refresh(); + + me.field = f; + if(old_text && me.field.df.fieldtype===cur.fieldtype) { + me.field.set_value(old_text); + } + + // run on enter + $(me.field.wrapper).find(':input').keydown(function(ev) { + if(ev.which==13) { + me.flist.base_list.run(); + } + }) + }, + + set_fieldtype: function(df, fieldtype) { + // reset + if(df.original_type) + df.fieldtype = df.original_type; + else + df.original_type = df.fieldtype; + + df.description = ''; df.reqd = 0; + df.ignore_link_validation = true; + + // given + if(fieldtype) { + df.fieldtype = fieldtype; + return; + } + + // scrub + if(df.fieldname=="docstatus") { + df.fieldtype="Select", + df.options=[ + {value:0, label:__("Draft")}, + {value:1, label:__("Submitted")}, + {value:2, label:__("Cancelled")} + ] + } else if(df.fieldtype=='Check') { + df.fieldtype='Select'; + df.options='No\nYes'; + } else if(['Text','Small Text','Text Editor','Code','Tag','Comments', + 'Dynamic Link','Read Only','Assign'].indexOf(df.fieldtype)!=-1) { + df.fieldtype = 'Data'; + } else if(df.fieldtype=='Link' && ['=', '!='].indexOf(this.wrapper.find('.condition').val())==-1) { + df.fieldtype = 'Data'; + } + if(df.fieldtype==="Data" && (df.options || "").toLowerCase()==="email") { + df.options = null; + } + if(this.wrapper.find('.condition').val()== "Between" && (df.fieldtype == 'Date' || df.fieldtype == 'Datetime')){ + df.fieldtype = 'DateRange'; + } + }, + + set_default_condition: function(df, fieldtype) { + if(!fieldtype) { + // set as "like" for data fields + if (df.fieldtype == 'Data') { + this.wrapper.find('.condition').val('like'); + } else if (df.fieldtype == 'Date' || df.fieldtype == 'Datetime'){ + this.wrapper.find('.condition').val('Between'); + }else{ + this.wrapper.find('.condition').val('='); + } + } + }, + + get_value: function() { + return [this.fieldselect.selected_doctype, + this.field.df.fieldname, this.get_condition(), this.get_selected_value()]; + }, + + get_selected_value: function() { + var val = this.field.get_value(); + + if(typeof val==='string') { + val = strip(val); + } + + if(this.field.df.original_type == 'Check') { + val = (val=='Yes' ? 1 :0); + } + + if(this.get_condition().indexOf('like', 'not like')!==-1) { + // automatically append wildcards + if(val) { + if(val.slice(0,1) !== "%") { + val = "%" + val; + } + if(val.slice(-1) !== "%") { + val = val + "%"; + } + } + } else if(in_list(["in", "not in"], this.get_condition())) { + if(val) { + val = $.map(val.split(","), function(v) { return strip(v); }); + } + } if(val === '%') { + val = ""; + } + + return val; + }, + + get_condition: function() { + return this.wrapper.find('.condition').val(); + }, + + freeze: function() { + if(this.$btn_group) { + // already made, just hide the condition setter + this.set_filter_button_text(); + this.wrapper.toggle(false); + return; + } + + var me = this; + + // add a button for new filter if missing + this.$btn_group = $(`
+ +
`) + .insertAfter(this.flist.wrapper.find(".set-filters .new-filter")); + + this.set_filter_button_text(); + + this.$btn_group.find(".remove-filter").on("click", function() { + me.remove(); + }); + + this.$btn_group.find(".toggle-filter").on("click", function() { + $(this).closest('.show_filters').find('.filter_area').show() + me.wrapper.toggle(); + }) + this.wrapper.toggle(false); + }, + + set_filter_button_text: function() { + var value = this.get_selected_value(); + value = this.flist.get_formatted_value(this.field, value); + + // for translations + // __("like"), __("not like"), __("in") + + this.$btn_group.find(".toggle-filter") + .html(repl('%(label)s %(condition)s "%(value)s"', { + label: __(this.field.df.label), + condition: __(this.get_condition()), + value: __(value), + })); + } + +}); + +// ') + .appendTo(this.parent) + .on("click", function () { $(this).select(); }); + this.select_input = this.$select.get(0); + this.awesomplete = new Awesomplete(this.select_input, { + minChars: 0, + maxItems: 99, + autoFirst: true, + list: me.options, + item: function(item, input) { + return $(repl('
  • %(label)s

  • ', item)) + .data("item.autocomplete", item) + .get(0); + } + }); + this.$select.on("awesomplete-select", function(e) { + var o = e.originalEvent; + var value = o.text.value; + var item = me.awesomplete.get_item(value); + me.selected_doctype = item.doctype; + me.selected_fieldname = item.fieldname; + if(me.select) me.select(item.doctype, item.fieldname); + }); + this.$select.on("awesomplete-selectcomplete", function(e) { + var o = e.originalEvent; + var value = o.text.value; + var item = me.awesomplete.get_item(value); + me.$select.val(item.label); + }); + + if(this.filter_fields) { + for(var i in this.filter_fields) + this.add_field_option(this.filter_fields[i]) + } else { + this.build_options(); + } + this.set_value(this.doctype, "name"); + window.last_filter = this; + }, + get_value: function() { + return this.selected_doctype ? this.selected_doctype + "." + this.selected_fieldname : null; + }, + val: function(value) { + if(value===undefined) { + return this.get_value(); + } else { + this.set_value(value); + } + }, + clear: function() { + this.selected_doctype = null; + this.selected_fieldname = null; + this.$select.val(""); + }, + set_value: function(doctype, fieldname) { + var me = this; + this.clear(); + if(!doctype) return; + + // old style + if(doctype.indexOf(".")!==-1) { + var parts = doctype.split("."); + doctype = parts[0]; + fieldname = parts[1]; + } + + $.each(this.options, function(i, v) { + if(v.doctype===doctype && v.fieldname===fieldname) { + me.selected_doctype = doctype; + me.selected_fieldname = fieldname; + me.$select.val(v.label); + return false; + } + }); + }, + build_options: function() { + var me = this; + me.table_fields = []; + var std_filters = $.map(frappe.model.std_fields, function(d) { + var opts = {parent: me.doctype} + if(d.fieldname=="name") opts.options = me.doctype; + return $.extend(copy_dict(d), opts); + }); + + // add parenttype column + var doctype_obj = locals['DocType'][me.doctype]; + if(doctype_obj && cint(doctype_obj.istable)) { + std_filters = std_filters.concat([{ + fieldname: 'parent', + fieldtype: 'Data', + label: 'Parent', + parent: me.doctype, + }]); + } + + // blank + if(this.with_blank) { + this.options.push({ + label:"", + value:"", + }) + } + + // main table + var main_table_fields = std_filters.concat(frappe.meta.docfield_list[me.doctype]); + $.each(frappe.utils.sort(main_table_fields, "label", "string"), function(i, df) { + // show fields where user has read access and if report hide flag is not set + if(frappe.perm.has_perm(me.doctype, df.permlevel, "read") && !df.report_hide) + me.add_field_option(df); + }); + + // child tables + $.each(me.table_fields, function(i, table_df) { + if(table_df.options) { + var child_table_fields = [].concat(frappe.meta.docfield_list[table_df.options]); + $.each(frappe.utils.sort(child_table_fields, "label", "string"), function(i, df) { + // show fields where user has read access and if report hide flag is not set + if(frappe.perm.has_perm(me.doctype, df.permlevel, "read") && !df.report_hide) + me.add_field_option(df); + }); + } + }); + }, + + add_field_option: function(df) { + var me = this; + if(me.doctype && df.parent==me.doctype) { + var label = __(df.label); + var table = me.doctype; + if(df.fieldtype=='Table') me.table_fields.push(df); + } else { + var label = __(df.label) + ' (' + __(df.parent) + ')'; + var table = df.parent; + } + if(frappe.model.no_value_type.indexOf(df.fieldtype) == -1 && + !(me.fields_by_name[df.parent] && me.fields_by_name[df.parent][df.fieldname])) { + this.options.push({ + label: label, + value: table + "." + df.fieldname, + fieldname: df.fieldname, + doctype: df.parent + }); + if(!me.fields_by_name[df.parent]) me.fields_by_name[df.parent] = {}; + me.fields_by_name[df.parent][df.fieldname] = df; + } + }, +})