resolved merged conflicts
This commit is contained in:
commit
8498bf8ea2
4 changed files with 1248 additions and 4 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
540
frappe/public/js/frappe/ui/base_list.js
Normal file
540
frappe/public/js/frappe/ui/base_list.js
Normal file
|
|
@ -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 $('<button class="btn btn-default"></button>')
|
||||
.appendTo(this.wrapper.find('.list-toolbar'))
|
||||
.html((icon ? ('<i class="' + icon + '"></i> ') : '') + 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) {
|
||||
$(`<span class="octicon octicon-search text-muted small"></span>`)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
691
frappe/public/js/frappe/ui/filters/filters.js
Normal file
691
frappe/public/js/frappe/ui/filters/filters.js
Normal file
|
|
@ -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(`
|
||||
<div class="show_filters">
|
||||
<div class="set-filters">
|
||||
<button
|
||||
style="margin-right: 10px;"
|
||||
class="btn btn-default btn-xs new-filter text-muted">
|
||||
${__("Add Filter")}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_area"></div>`);
|
||||
},
|
||||
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 = $('<div class="text-muted small">').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"))+'</div>');
|
||||
} 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 = $(`<div class="btn-group">
|
||||
<button class="btn btn-default btn-xs toggle-filter"
|
||||
title="${ __("Edit Filter") }">
|
||||
</button>
|
||||
<button class="btn btn-default btn-xs remove-filter"
|
||||
title="${ __("Remove Filter") }">
|
||||
<i class="fa fa-remove text-muted"></i>
|
||||
</button></div>`)
|
||||
.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),
|
||||
}));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// <select> widget with all fields of a doctype as options
|
||||
frappe.ui.FieldSelect = Class.extend({
|
||||
// opts parent, doctype, filter_fields, with_blank, select
|
||||
init: function(opts) {
|
||||
var me = this;
|
||||
$.extend(this, opts);
|
||||
this.fields_by_name = {};
|
||||
this.options = [];
|
||||
this.$select = $('<input class="form-control">')
|
||||
.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('<li class="filter-field-select"><p>%(label)s</p></li>', 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;
|
||||
}
|
||||
},
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue