@@ -0,0 +1,57 @@ | |||
context('Control Autocomplete', () => { | |||
before(() => { | |||
cy.login(); | |||
cy.visit('/app/website'); | |||
}); | |||
function get_dialog_with_autocomplete(options) { | |||
cy.visit('/app/website'); | |||
return cy.dialog({ | |||
title: 'Autocomplete', | |||
fields: [ | |||
{ | |||
'label': 'Select an option', | |||
'fieldname': 'autocomplete', | |||
'fieldtype': 'Autocomplete', | |||
'options': options || ['Option 1', 'Option 2', 'Option 3'], | |||
} | |||
] | |||
}); | |||
} | |||
it('should set the valid value', () => { | |||
get_dialog_with_autocomplete().as('dialog'); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').focus().as('input'); | |||
cy.wait(1000); | |||
cy.get('@input').type('2', { delay: 300 }); | |||
cy.get('.frappe-control[data-fieldname=autocomplete]').findByRole('listbox').should('be.visible'); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').type('{enter}', { delay: 300 }); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').blur(); | |||
cy.get('@dialog').then(dialog => { | |||
let value = dialog.get_value('autocomplete'); | |||
expect(value).to.eq('Option 2'); | |||
dialog.clear(); | |||
}); | |||
}); | |||
it('should set the valid value with different label', () => { | |||
const options_with_label = [ | |||
{ label: "Option 1", value: "option_1" }, | |||
{ label: "Option 2", value: "option_2" } | |||
]; | |||
get_dialog_with_autocomplete(options_with_label).as('dialog'); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').focus().as('input'); | |||
cy.get('.frappe-control[data-fieldname=autocomplete]').findByRole('listbox').should('be.visible'); | |||
cy.get('@input').type('2', { delay: 300 }); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').type('{enter}', { delay: 300 }); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').blur(); | |||
cy.get('@dialog').then(dialog => { | |||
let value = dialog.get_value('autocomplete'); | |||
expect(value).to.eq('option_2'); | |||
dialog.clear(); | |||
}); | |||
}); | |||
}); |
@@ -99,7 +99,7 @@ | |||
"label": "Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
@@ -547,7 +547,7 @@ | |||
"index_web_pages_for_search": 1, | |||
"istable": 1, | |||
"links": [], | |||
"modified": "2022-01-27 21:22:20.529072", | |||
"modified": "2022-02-14 11:56:19.812863", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocField", | |||
@@ -330,7 +330,8 @@ result = [ | |||
} | |||
] | |||
result = add_total_row(result, columns, meta=None, report_settings=report_settings) | |||
result = add_total_row(result, columns, meta=None, is_tree=report_settings['tree'], | |||
parent_field=report_settings['parent_field']) | |||
self.assertEqual(result[-1][0], "Total") | |||
self.assertEqual(result[-1][1], 200) | |||
self.assertEqual(result[-1][2], 150.50) |
@@ -122,7 +122,7 @@ | |||
"label": "Field Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break", | |||
"reqd": 1 | |||
}, | |||
{ | |||
@@ -431,7 +431,7 @@ | |||
"idx": 1, | |||
"index_web_pages_for_search": 1, | |||
"links": [], | |||
"modified": "2022-01-27 21:47:01.065556", | |||
"modified": "2022-02-14 15:42:21.885999", | |||
"modified_by": "Administrator", | |||
"module": "Custom", | |||
"name": "Custom Field", | |||
@@ -85,7 +85,7 @@ | |||
"label": "Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
@@ -450,7 +450,7 @@ | |||
"index_web_pages_for_search": 1, | |||
"istable": 1, | |||
"links": [], | |||
"modified": "2022-02-08 19:38:16.111199", | |||
"modified": "2022-02-25 16:01:12.616736", | |||
"modified_by": "Administrator", | |||
"module": "Custom", | |||
"name": "Customize Form Field", | |||
@@ -460,4 +460,4 @@ | |||
"sort_field": "modified", | |||
"sort_order": "ASC", | |||
"states": [] | |||
} | |||
} |
@@ -52,7 +52,8 @@ class MariaDBDatabase(Database): | |||
'Barcode': ('longtext', ''), | |||
'Geolocation': ('longtext', ''), | |||
'Duration': ('decimal', '21,9'), | |||
'Icon': ('varchar', self.VARCHAR_LEN) | |||
'Icon': ('varchar', self.VARCHAR_LEN), | |||
'Autocomplete': ('varchar', self.VARCHAR_LEN), | |||
} | |||
def get_connection(self): | |||
@@ -62,7 +62,8 @@ class PostgresDatabase(Database): | |||
'Barcode': ('text', ''), | |||
'Geolocation': ('text', ''), | |||
'Duration': ('decimal', '21,9'), | |||
'Icon': ('varchar', self.VARCHAR_LEN) | |||
'Icon': ('varchar', self.VARCHAR_LEN), | |||
'Autocomplete': ('varchar', self.VARCHAR_LEN), | |||
} | |||
def get_connection(self): | |||
@@ -11,8 +11,10 @@ from frappe.model.utils.user_settings import get_user_settings | |||
from frappe.permissions import get_doc_permissions | |||
from frappe.desk.form.document_follow import is_document_followed | |||
from frappe import _ | |||
from frappe import _dict | |||
from urllib.parse import quote | |||
@frappe.whitelist() | |||
def getdoc(doctype, name, user=None): | |||
""" | |||
@@ -50,8 +52,11 @@ def getdoc(doctype, name, user=None): | |||
doc.add_seen() | |||
set_link_titles(doc) | |||
if frappe.response.docs is None: | |||
frappe.response = _dict({"docs": []}) | |||
frappe.response.docs.append(doc) | |||
@frappe.whitelist() | |||
def getdoctype(doctype, with_parent=False, cached_timestamp=None): | |||
"""load doctype""" | |||
@@ -392,7 +392,7 @@ def make_records(records, debug=False): | |||
doc.flags.ignore_mandatory = True | |||
try: | |||
doc.insert(ignore_permissions=True, ignore_if_duplicate=True) | |||
doc.insert(ignore_permissions=True) | |||
frappe.db.commit() | |||
except frappe.DuplicateEntryError as e: | |||
@@ -73,7 +73,7 @@ def get_report_result(report, filters): | |||
return res | |||
@frappe.read_only() | |||
def generate_report_result(report, filters=None, user=None, custom_columns=None, report_settings=None): | |||
def generate_report_result(report, filters=None, user=None, custom_columns=None, is_tree=False, parent_field=None): | |||
user = user or frappe.session.user | |||
filters = filters or [] | |||
@@ -108,7 +108,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None, | |||
result = get_filtered_data(report.ref_doctype, columns, result, user) | |||
if cint(report.add_total_row) and result and not skip_total_row: | |||
result = add_total_row(result, columns, report_settings=report_settings) | |||
result = add_total_row(result, columns, is_tree=is_tree, parent_field=parent_field) | |||
return { | |||
"result": result, | |||
@@ -210,7 +210,7 @@ def get_script(report_name): | |||
@frappe.whitelist() | |||
@frappe.read_only() | |||
def run(report_name, filters=None, user=None, ignore_prepared_report=False, custom_columns=None, report_settings=None): | |||
def run(report_name, filters=None, user=None, ignore_prepared_report=False, custom_columns=None, is_tree=False, parent_field=None): | |||
report = get_report_doc(report_name) | |||
if not user: | |||
user = frappe.session.user | |||
@@ -238,7 +238,7 @@ def run(report_name, filters=None, user=None, ignore_prepared_report=False, cust | |||
dn = "" | |||
result = get_prepared_report_result(report, filters, dn, user) | |||
else: | |||
result = generate_report_result(report, filters, user, custom_columns, report_settings) | |||
result = generate_report_result(report, filters, user, custom_columns, is_tree, parent_field) | |||
result["add_total_row"] = report.add_total_row and not result.get( | |||
"skip_total_row", False | |||
@@ -435,18 +435,9 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation, ignore_visi | |||
return result, column_widths | |||
def add_total_row(result, columns, meta=None, report_settings=None): | |||
def add_total_row(result, columns, meta=None, is_tree=False, parent_field=None): | |||
total_row = [""] * len(columns) | |||
has_percent = [] | |||
is_tree = False | |||
parent_field = '' | |||
if report_settings: | |||
if isinstance(report_settings, (str,)): | |||
report_settings = json.loads(report_settings) | |||
is_tree = report_settings.get('tree') | |||
parent_field = report_settings.get('parent_field') | |||
for i, col in enumerate(columns): | |||
fieldtype, options, fieldname = None, None, None | |||
@@ -35,7 +35,8 @@ data_fieldtypes = ( | |||
'Barcode', | |||
'Geolocation', | |||
'Duration', | |||
'Icon' | |||
'Icon', | |||
'Autocomplete', | |||
) | |||
attachment_fieldtypes = ( | |||
@@ -1154,7 +1154,7 @@ class Document(BaseDocument): | |||
for f in hooks: | |||
add_to_return_value(self, f(self, method, *args, **kwargs)) | |||
return self._return_value | |||
return self.__dict__.pop("_return_value", None) | |||
return runner | |||
@@ -11,7 +11,26 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
set_options() { | |||
if (this.df.options) { | |||
let options = this.df.options || []; | |||
this._data = this.parse_options(options); | |||
this.set_data(options); | |||
} | |||
} | |||
format_for_input(value) { | |||
if (value == null) { | |||
return ""; | |||
} else if (this._data && this._data.length) { | |||
const item = this._data.find(i => i.value == value); | |||
return item ? item.label : value; | |||
} else { | |||
return value; | |||
} | |||
} | |||
get_input_value() { | |||
if (this.$input) { | |||
const label = this.$input.val(); | |||
const item = this._data?.find(i => i.label == label); | |||
return item ? item.value : label; | |||
} | |||
} | |||
@@ -23,7 +42,7 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
autoFirst: true, | |||
list: this.get_data(), | |||
data: function(item) { | |||
if (!(item instanceof Object)) { | |||
if (typeof item !== 'object') { | |||
var d = { value: item }; | |||
item = d; | |||
} | |||
@@ -65,6 +84,18 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
}; | |||
} | |||
init_option_cache() { | |||
if (!this.$input.cache) { | |||
this.$input.cache = {}; | |||
} | |||
if (!this.$input.cache[this.doctype]) { | |||
this.$input.cache[this.doctype] = {}; | |||
} | |||
if (!this.$input.cache[this.doctype][this.df.fieldname]) { | |||
this.$input.cache[this.doctype][this.df.fieldname] = {}; | |||
} | |||
} | |||
setup_awesomplete() { | |||
this.awesomplete = new Awesomplete( | |||
this.input, | |||
@@ -75,12 +106,18 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
.find('.awesomplete ul') | |||
.css('min-width', '100%'); | |||
this.$input.on( | |||
'input', | |||
frappe.utils.debounce(() => { | |||
this.init_option_cache(); | |||
this.$input.on('input', frappe.utils.debounce((e) => { | |||
const cached_options = this.$input.cache[this.doctype][this.df.fieldname][e.target.value]; | |||
if (cached_options && cached_options.length) { | |||
this.set_data(cached_options); | |||
} else if (this.get_query || this.df.get_query) { | |||
this.execute_query_if_exists(e.target.value); | |||
} else { | |||
this.awesomplete.list = this.get_data(); | |||
}, 500) | |||
); | |||
} | |||
}, 500)); | |||
this.$input.on('focus', () => { | |||
if (!this.$input.val()) { | |||
@@ -89,6 +126,17 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
} | |||
}); | |||
this.$input.on("blur", () => { | |||
if(this.selected) { | |||
this.selected = false; | |||
return; | |||
} | |||
var value = this.get_input_value(); | |||
if(value!==this.last_value) { | |||
this.parse_validate_and_set_in_model(value); | |||
} | |||
}); | |||
this.$input.on("awesomplete-open", () => { | |||
this.autocomplete_open = true; | |||
}); | |||
@@ -127,6 +175,75 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
return options; | |||
} | |||
execute_query_if_exists(term) { | |||
const args = { txt: term }; | |||
let get_query = this.get_query || this.df.get_query; | |||
if (!get_query) { | |||
return; | |||
} | |||
let set_nulls = function(obj) { | |||
$.each(obj, function(key, value) { | |||
if (value !== undefined) { | |||
obj[key] = value; | |||
} | |||
}); | |||
return obj; | |||
}; | |||
let process_query_object = function(obj) { | |||
if (obj.query) { | |||
args.query = obj.query; | |||
} | |||
if (obj.params) { | |||
set_nulls(obj.params); | |||
Object.assign(args, obj.params); | |||
} | |||
// turn off value translation | |||
if (obj.translate_values !== undefined) { | |||
this.translate_values = obj.translate_values; | |||
} | |||
}; | |||
if ($.isPlainObject(get_query)) { | |||
process_query_object(get_query); | |||
} else if (typeof get_query === "string") { | |||
args.query = get_query; | |||
} else { | |||
// get_query by function | |||
var q = get_query( | |||
(this.frm && this.frm.doc) || this.doc, | |||
this.doctype, | |||
this.docname | |||
); | |||
if (typeof q === "string") { | |||
// returns a string | |||
args.query = q; | |||
} else if ($.isPlainObject(q)) { | |||
// returns an object | |||
process_query_object(q); | |||
} | |||
} | |||
if (args.query) { | |||
frappe.call({ | |||
method: args.query, | |||
args: args, | |||
callback: ({ message }) => { | |||
if(!this.$input.is(":focus")) { | |||
return; | |||
} | |||
this.$input.cache[this.doctype][this.df.fieldname][term] = message; | |||
this.set_data(message); | |||
} | |||
}) | |||
} | |||
} | |||
get_data() { | |||
return this._data || []; | |||
} | |||
@@ -21,6 +21,9 @@ frappe.form.formatters = { | |||
} | |||
return value==null ? "" : value; | |||
}, | |||
Autocomplete: function(value) { | |||
return __(frappe.form.formatters["Data"](value)); | |||
}, | |||
Select: function(value) { | |||
return __(frappe.form.formatters["Data"](value)); | |||
}, | |||
@@ -183,21 +183,20 @@ export default class GridRow { | |||
render_template() { | |||
this.set_row_index(); | |||
if(this.row_display) { | |||
if (this.row_display) { | |||
this.row_display.remove(); | |||
} | |||
// row index | |||
if(this.doc) { | |||
if(!this.row_index) { | |||
this.row_index = $('<div style="float: left; margin-left: 15px; margin-top: 8px; \ | |||
margin-right: -20px;">'+this.row_check_html+' <span></span></div>').appendTo(this.row); | |||
} | |||
if (!this.row_index) { | |||
this.row_index = $(`<div class="template-row-index">${this.row_check_html}<span></span></div>`).appendTo(this.row); | |||
} | |||
if (this.doc) { | |||
this.row_index.find('span').html(this.doc.idx); | |||
} | |||
this.row_display = $('<div class="row-data sortable-handle template-row">'+ | |||
+'</div>').appendTo(this.row) | |||
this.row_display = $('<div class="row-data sortable-handle template-row"></div>').appendTo(this.row) | |||
.html(frappe.render(this.grid.template, { | |||
doc: this.doc ? frappe.get_format_helper(this.doc) : null, | |||
frm: this.frm, | |||
@@ -578,7 +578,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { | |||
args: { | |||
report_name: this.report_name, | |||
filters: filters, | |||
report_settings: this.report_settings | |||
is_tree: this.report_settings.tree, | |||
parent_field: this.report_settings.parent_field | |||
}, | |||
callback: resolve, | |||
always: () => this.page.btn_secondary.prop('disabled', false) | |||
@@ -54,7 +54,7 @@ | |||
} | |||
.form-grid .grid-heading-row .template-row { | |||
margin-left: 20px; | |||
margin-left: 8px; | |||
} | |||
.form-grid .template-row { | |||
@@ -88,6 +88,17 @@ | |||
margin-top: 2px; | |||
} | |||
.template-row-index { | |||
float: left; | |||
margin-left: 15px; | |||
margin-top: 8px; | |||
margin-right: -20px; | |||
span { | |||
margin-left: 5px; | |||
} | |||
} | |||
.editable-form .grid-static-col.bold { | |||
font-weight: bold; | |||
} | |||
@@ -164,6 +164,7 @@ def get_alert_dict(doc): | |||
return alert_dict | |||
def create_energy_points_log(ref_doctype, ref_name, doc, apply_only_once=False): | |||
doc = frappe._dict(doc) | |||
@@ -171,7 +172,7 @@ def create_energy_points_log(ref_doctype, ref_name, doc, apply_only_once=False): | |||
ref_name, doc.rule, None if apply_only_once else doc.user) | |||
if log_exists: | |||
return | |||
return frappe.get_doc('Energy Point Log', log_exists) | |||
new_log = frappe.new_doc('Energy Point Log') | |||
new_log.reference_doctype = ref_doctype | |||