diff --git a/cypress/integration/control_link.js b/cypress/integration/control_link.js index 8f9257e9c4..e691150925 100644 --- a/cypress/integration/control_link.js +++ b/cypress/integration/control_link.js @@ -65,16 +65,49 @@ context('Control Link', () => { cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link'); cy.get('@todos').then(todos => { - cy.get('.frappe-control[data-fieldname=link] input').as('input'); - cy.get('@input').focus(); + cy.get('.frappe-control[data-fieldname=link] input').focus().as('input'); cy.wait('@search_link'); - cy.get('@input').type(todos[0]).blur(); + cy.get('@input').type(todos[0]); + cy.wait('@search_link'); + cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible'); + cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 }); cy.wait('@validate_link'); cy.get('@input').focus(); cy.get('.frappe-control[data-fieldname=link] .link-btn') .should('be.visible') .click(); - cy.location('pathname').should('eq', `/app/todo/${todos[0]}`); + cy.location('hash').should('eq', `#Form/ToDo/${todos[0]}`); + }); + }); + + it('show title field in link', () => { + get_dialog_with_link().as('dialog'); + + cy.server(); + cy.insert_doc("Property Setter", { + property: "show_title_field_in_link", + doc_type: "ToDo", + value: 1, + doctype_or_field: "DocType" + }, true); + cy.route('POST', '/api/method/frappe.desk.search.search_link').as('search_link'); + + cy.get('.frappe-control[data-fieldname=link] input').focus().as('input'); + cy.wait('@search_link'); + cy.get('@input').type('todo for link'); + cy.wait('@search_link'); + cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible'); + cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 }); + cy.get('.frappe-control[data-fieldname=link] input').blur(); + cy.get('@dialog').then(dialog => { + cy.get('@todos').then(todos => { + let field = dialog.get_field('link'); + let value = field.get_value(); + let label = field.get_label_value(); + + expect(value).to.eq(todos[0]); + expect(label).to.eq('this is a test todo for link'); + }); }); }); }); diff --git a/frappe/boot.py b/frappe/boot.py index 0589e32ac8..8f02b67934 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -87,6 +87,7 @@ def get_bootinfo(): bootinfo.additional_filters_config = get_additional_filters_from_hooks() bootinfo.desk_settings = get_desk_settings() bootinfo.app_logo_url = get_app_logo() + bootinfo.doctypes_with_show_link_field_title = doctypes_with_show_link_field_title() return bootinfo @@ -324,3 +325,9 @@ def get_desk_settings(): def get_notification_settings(): return frappe.get_cached_doc('Notification Settings', frappe.session.user) + +def doctypes_with_show_link_field_title(): + dts = frappe.get_all("DocType", {"show_title_field_in_link": 1}) + custom_dts = frappe.get_all("Property Setter", {"field_name": "show_title_field_in_link", "value": 1}) + + return [d.name for d in dts + custom_dts if d] \ No newline at end of file diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 6a427f71e1..3024fb32a2 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -45,6 +45,7 @@ "allow_auto_repeat", "view_settings", "title_field", + "show_title_field_in_link", "search_fields", "default_print_format", "sort_field", @@ -554,6 +555,12 @@ "fieldname": "website_search_field", "fieldtype": "Data", "label": "Website Search Field" + }, + { + "default": "0", + "fieldname": "show_title_field_in_link", + "fieldtype": "Check", + "label": "Show Title in Link and Table MultiSelect Field" } ], "icon": "fa fa-bolt", @@ -635,7 +642,7 @@ "link_fieldname": "reference_doctype" } ], - "modified": "2021-06-17 23:31:44.974199", + "modified": "2021-08-03 13:41:50.319555", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index c2940a92e3..cdcac1582a 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -27,6 +27,7 @@ "autoname", "view_settings_section", "title_field", + "show_title_field_in_link", "image_field", "default_print_format", "column_break_29", @@ -280,6 +281,12 @@ "fieldname": "autoname", "fieldtype": "Data", "label": "Auto Name" + }, + { + "default": "0", + "fieldname": "show_title_field_in_link", + "fieldtype": "Check", + "label": "Show Title in Link and Table MultiSelect Field" } ], "hide_toolbar": 1, @@ -288,7 +295,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-06-21 19:01:06.920663", + "modified": "2021-08-03 13:43:27.938781", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 8de194fb00..1866f4d368 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -495,7 +495,8 @@ doctype_properties = { 'email_append_to': 'Check', 'subject_field': 'Data', 'sender_field': 'Data', - 'autoname': 'Data' + 'autoname': 'Data', + 'show_title_field_in_link': 'Check' } docfield_properties = { diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql index f8841e9417..a74ece8478 100644 --- a/frappe/database/mariadb/framework_mariadb.sql +++ b/frappe/database/mariadb/framework_mariadb.sql @@ -224,6 +224,7 @@ CREATE TABLE `tabDocType` ( `email_append_to` int(1) NOT NULL DEFAULT 0, `subject_field` varchar(255) DEFAULT NULL, `sender_field` varchar(255) DEFAULT NULL, + `show_title_field_in_link` int(1) NOT NULL DEFAULT 0, PRIMARY KEY (`name`), KEY `parent` (`parent`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql index a4e94aa326..f65a832bc6 100644 --- a/frappe/database/postgres/framework_postgres.sql +++ b/frappe/database/postgres/framework_postgres.sql @@ -229,6 +229,7 @@ CREATE TABLE "tabDocType" ( "email_append_to" smallint NOT NULL DEFAULT 0, "subject_field" varchar(255) DEFAULT NULL, "sender_field" varchar(255) DEFAULT NULL, + "show_title_field_in_link" smallint NOT NULL DEFAULT 0, PRIMARY KEY ("name") ) ; diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index a62bfd01d0..994a50f938 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -48,7 +48,7 @@ def getdoc(doctype, name, user=None): raise doc.add_seen() - + set_link_titles(doc) frappe.response.docs.append(doc) @frappe.whitelist(allow_guest=True) @@ -310,3 +310,55 @@ def get_additional_timeline_content(doctype, docname): contents.extend(frappe.get_attr(method)(doctype, docname) or []) return contents + +def set_link_titles(doc): + meta = frappe.get_meta(doc.doctype) + link_titles = {} + link_titles.update(get_title_values_for_link_and_dynamic_link_fields(meta, doc)) + link_titles.update(get_title_values_for_table_and_multiselect_fields(meta, doc)) + + send_link_titles(link_titles) + +def get_title_values_for_link_and_dynamic_link_fields(meta, doc, link_fields=None): + link_titles = {} + + if not link_fields: + link_fields = meta.get_link_fields() + meta.get_dynamic_link_fields() + + for field in link_fields: + if not doc.get(field.fieldname): + continue + + doctype = field.options if field.fieldtype == "Link" else doc.get(field.options) + + meta = frappe.get_meta(doctype) + if not meta or not (meta.title_field and meta.show_title_field_in_link): + continue + + link_title = frappe.get_cached_value(doctype, doc.get(field.fieldname), meta.title_field) + link_titles.update({doctype + "::" + doc.get(field.fieldname): link_title}) + + return link_titles + +def get_title_values_for_table_and_multiselect_fields(meta, doc, table_fields=None): + link_titles = {} + + if not table_fields: + table_fields = meta.get_table_fields() + + for field in table_fields: + if not doc.get(field.fieldname): + continue + + _meta = frappe.get_meta(field.options) + for value in doc.get(field.fieldname): + link_titles.update(get_title_values_for_link_and_dynamic_link_fields(_meta, value)) + + return link_titles + +def send_link_titles(link_titles): + """Append link titles dict in `frappe.local.response`.""" + if "_link_titles" not in frappe.local.response: + frappe.local.response["_link_titles"] = {} + + frappe.local.response["_link_titles"].update(link_titles) diff --git a/frappe/desk/search.py b/frappe/desk/search.py index f9b65fc98e..a64d6efec4 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -49,8 +49,11 @@ def sanitize_searchfield(searchfield): # this is called by the Link Field @frappe.whitelist() def search_link(doctype, txt, query=None, filters=None, page_length=20, searchfield=None, reference_doctype=None, ignore_user_permissions=False): - search_widget(doctype, txt.strip(), query, searchfield=searchfield, page_length=page_length, filters=filters, reference_doctype=reference_doctype, ignore_user_permissions=ignore_user_permissions) - frappe.response['results'] = build_for_autosuggest(frappe.response["values"]) + search_widget(doctype, txt.strip(), query, searchfield=searchfield, page_length=page_length, filters=filters, + reference_doctype=reference_doctype, ignore_user_permissions=ignore_user_permissions) + + frappe.response["results"] = build_for_autosuggest(frappe.response["values"], doctype=doctype, + is_query=True if query else False) del frappe.response["values"] # this is called by the search box @@ -138,6 +141,11 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, fields = list(set(fields + json.loads(filter_fields))) formatted_fields = ['`tab%s`.`%s`' % (meta.name, f.strip()) for f in fields] + title_field_query = get_title_field_query(meta) + + # Insert title field query after name + formatted_fields.insert(1, title_field_query) + # find relevance as location of search term from the beginning of string `name`. used for sorting results. formatted_fields.append("""locate({_txt}, `tab{doctype}`.`name`) as `_relevance`""".format( _txt=frappe.db.escape((txt or "").replace("%", "").replace("@", "")), doctype=doctype)) @@ -205,10 +213,32 @@ def get_std_fields_list(meta, key): return sflist -def build_for_autosuggest(res): +def get_title_field_query(meta): + title_field = meta.title_field if meta.title_field else None + show_title_field_in_link = meta.show_title_field_in_link if meta.show_title_field_in_link else None + field = "NULL as `label`" + + if title_field and show_title_field_in_link: + field = "`tab{0}`.{1} as `label`".format(meta.name, title_field) + + return field + +def build_for_autosuggest(res, doctype, is_query): results = [] for r in res: - out = {"value": r[0], "description": ", ".join(unique(cstr(d) for d in r if d)[1:])} + r = list(r) + if is_query or doctype in (frappe.get_hooks().standard_queries or {}): + out = { + "value": r[0], + "description": ", ".join(unique(cstr(d) for d in r[1:] if d)) + } + else: + out = { + "value": r[0], + "label": r[1], + "description": ", ".join(unique(cstr(d) for d in r[2:] if d)) + } + results.append(out) return results @@ -271,3 +301,12 @@ def get_user_groups(): return frappe.get_all('User Group', fields=['name as id', 'name as value'], update={ 'is_group': True }) + +@frappe.whitelist() +def get_link_title(doctype, docname): + meta = frappe.get_meta(doctype) + + if meta.title_field and meta.show_title_field_in_link: + return frappe.get_cached_value(doctype, docname, meta.title_field) + + return docname \ No newline at end of file diff --git a/frappe/public/js/controls.bundle.js b/frappe/public/js/controls.bundle.js index 30b5d43905..b08cbb336d 100644 --- a/frappe/public/js/controls.bundle.js +++ b/frappe/public/js/controls.bundle.js @@ -16,3 +16,4 @@ import "air-datepicker/dist/js/i18n/datepicker.sk.js"; import "air-datepicker/dist/js/i18n/datepicker.zh.js"; import "./frappe/ui/capture.js"; import "./frappe/form/controls/control.js"; +import "./frappe/link_title.js"; diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 83f3f8dd70..82481d5303 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -29,12 +29,14 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat setTimeout(function() { if(me.$input.val() && me.get_options()) { let doctype = me.get_options(); - let name = me.$input.val(); + let name = me.get_input_value(); me.$link.toggle(true); me.$link_open.attr('href', frappe.utils.get_form_link(doctype, name)); } if(!me.$input.val()) { + me.reset_value(); + me.reset_fetch_values(me.df, me.docname); me.$input.val("").trigger("input"); } }, 500); @@ -69,6 +71,76 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat this.$input_area.find(".link-btn").remove(); } } + set_formatted_input(value) { + super.set_formatted_input(); + if (!value) return; + let doctype = this.get_options(); + this.set_data_value(frappe.get_link_title(doctype, value) || value, value); + } + set_data_value(link_display, value) { + if (!this.$input) { + return; + } + + this.$input.val(__(link_display)); + this.data_value = value; + } + parse_validate_and_set_in_model(value, label, e) { + if (this.parse) value = this.parse(value, label); + if (label) { + this.label = label; + frappe.add_link_title(this.doctype, value, label); + } + + return this.validate_and_set_in_model(value, e); + } + validate_and_set_in_model(value, e) { + var me = this; + if (this.inside_change_event) { + return Promise.resolve(); + } + this.inside_change_event = true; + var set = function(value) { + me.inside_change_event = false; + return frappe.run_serially([ + () => me.set_model_value(value), + () => { + me.set_mandatory && me.set_mandatory(value); + + if (me.df.change || me.df.onchange) { + // onchange event specified in df + frappe.set_link_title(me); + return (me.df.change || me.df.onchange).apply(me, [e]); + } + } + ]); + }; + + value = this.validate(value); + if (value && value.then) { + // got a promise + return value.then((value) => set(value)); + } else { + // all clear + return set(value); + } + } + get_input_value() { + return (this.$input && this.data_value && this.$input.val()) ? this.data_value : ""; + } + get_label_value() { + return this.$input ? this.$input.val() : ""; + } + set_input_label(label) { + this.$input && this.$input.val(__(label)); + } + reset_value() { + if (!this.$input) { + return; + } + this.$input.val(""); + this.data_value = null; + } open_advanced_search() { var doctype = this.get_options(); if(!doctype) return; @@ -98,7 +170,7 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat } // partially entered name field - frappe.route_options.name_field = this.get_value(); + frappe.route_options.name_field = this.get_label_value(); // reference to calling link frappe._from_link = this; @@ -120,6 +192,11 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat maxItems: 99, autoFirst: true, list: [], + replace: function (suggestion) { + // Override Awesomeplete replace function as it is used to set the input value + // https://github.com/LeaVerou/awesomplete/issues/17104#issuecomment-359185403 + this.input.value = suggestion.label || suggestion.value; + }, data: function (item) { return { label: item.label || item.value, @@ -236,9 +313,11 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat me.selected = false; return; } - var value = me.get_input_value(); - if(value!==me.last_value) { - me.parse_validate_and_set_in_model(value); + let value = me.get_input_value(); + let label = me.get_label_value(); + + if (value !== me.last_value || me.label !== label) { + me.parse_validate_and_set_in_model(value, label); } }); @@ -258,14 +337,15 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat // prevent selection on tab var TABKEY = 9; - if(e.keyCode === TABKEY) { + if (e.keyCode === TABKEY) { e.preventDefault(); me.awesomplete.close(); return false; } - if(item.action) { + if (item.action) { item.value = ""; + item.label = ""; item.action.apply(me); } @@ -277,13 +357,14 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat frappe.boot.user.last_selected_values[me.df.options] = item.value; } - me.parse_validate_and_set_in_model(item.value); + me.parse_validate_and_set_in_model(item.value, item.label); }); this.$input.on("awesomplete-selectcomplete", function(e) { - var o = e.originalEvent; - if(o.text.value.indexOf("__link_option") !== -1) { - me.$input.val(""); + let o = e.originalEvent; + if (o.text.value.indexOf("__link_option") !== -1) { + me.reset_value(); + me.reset_fetch_values(me.df, me.docname); } }); } @@ -452,10 +533,13 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat this.docname, value); } validate_link_and_fetch(df, doctype, docname, value) { - if(value) { + let me = this; + + if (value) { return new Promise((resolve) => { - var fetch = ''; - if(this.frm && this.frm.fetch_dict[df.fieldname]) { + let fetch = ''; + + if (this.frm && this.frm.fetch_dict[df.fieldname]) { fetch = this.frm.fetch_dict[df.fieldname].columns.join(', '); } // if default and no fetch, no need to validate @@ -465,10 +549,14 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat this.fetch_and_validate_link(resolve, df, doctype, docname, value, fetch); }); + } else { + me.reset_value(); + me.reset_fetch_values(df, docname); } } - fetch_and_validate_link(resolve, df, doctype, docname, value, fetch) { + let me = this; + frappe.call({ method: 'frappe.desk.form.utils.validate_link', type: "GET", @@ -485,18 +573,27 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat } resolve(r.valid_value); } else { + me.reset_value(); + me.reset_fetch_values(df, docname); resolve(""); } } }); } - set_fetch_values(df, docname, fetch_values) { - var fl = this.frm.fetch_dict[df.fieldname].fields; - for(var i=0; i < fl.length; i++) { + let fl = this.frm.fetch_dict[df.fieldname].fields; + + for (var i=0; i < fl.length; i++) { frappe.model.set_value(df.parent, docname, fl[i], fetch_values[i], df.fieldtype); } } + reset_fetch_values(df, docname) { + let fields = this.frm && this.frm.fetch_dict && this.frm.fetch_dict[df.fieldname] ? this.frm.fetch_dict[df.fieldname].fields : []; + + fields.forEach(field => { + frappe.model.set_value(df.parent, docname, field, null, df.fieldtype); + }); + } }; if (Awesomplete) { diff --git a/frappe/public/js/frappe/form/controls/table_multiselect.js b/frappe/public/js/frappe/form/controls/table_multiselect.js index 15dfd9649e..f74e0837ff 100644 --- a/frappe/public/js/frappe/form/controls/table_multiselect.js +++ b/frappe/public/js/frappe/form/controls/table_multiselect.js @@ -62,6 +62,7 @@ frappe.ui.form.ControlTableMultiSelect = class ControlTableMultiSelect extends f [link_field.fieldname]: value }); } + frappe.add_link_title(link_field.options, value, label); } this._rows_list = this.rows.map(row => row[link_field.fieldname]); return this.rows; @@ -126,10 +127,12 @@ frappe.ui.form.ControlTableMultiSelect = class ControlTableMultiSelect extends f this.$input_area.prepend(html); } get_pill_html(value) { - const encoded_value = encodeURIComponent(value); + const link_field = this.get_link_field(); + const encoded_value = encodeURIComponent(value.name); + const pill_name = frappe.get_link_title(link_field.options, value[link_field.fieldname]) || value.name; return ` `; diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index b9a838688d..c4f042533d 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -106,12 +106,14 @@ frappe.form.formatters = { Link: function(value, docfield, options, doc) { var doctype = docfield._options || docfield.options; var original_value = value; + let link_title = frappe.get_link_title(doctype, value); + if(value && value.match && value.match(/^['"].*['"]$/)) { value.replace(/^.(.*).$/, "$1"); } if(options && (options.for_print || options.only_value)) { - return value; + return link_title || value; } if(frappe.form.link_formatters[doctype]) { @@ -135,13 +137,14 @@ frappe.form.formatters = { return ` - ${__(options && options.label || value)}`; + data-name="${original_value}" + data-value="${original_value}"> + ${__(options && options.label || link_title || value)}`; } else { - return value; + return link_title || value; } } else { - return value; + return link_title || value; } }, Date: function(value) { diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index 65d84e2202..612e449a7e 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -249,31 +249,40 @@ frappe.ui.form.update_calling_link = (newdoc) => { }; if (is_valid_doctype()) { - // set value - if (doc && doc.parentfield) { - //update values for child table - $.each(frappe._from_link.frm.fields_dict[doc.parentfield].grid.grid_rows, function (index, field) { - if (field.doc && field.doc.name === frappe._from_link.docname) { - frappe._from_link.set_value(newdoc.name); - } - }); - } else { - frappe._from_link.set_value(newdoc.name); - } - - // refresh field - frappe._from_link.refresh(); - - // if from form, switch - if (frappe._from_link.frm) { - frappe.set_route("Form", - frappe._from_link.frm.doctype, frappe._from_link.frm.docname) - .then(() => { - frappe.utils.scroll_to(frappe._from_link_scrollY); + frappe.model.with_doctype(newdoc.doctype, () => { + let meta = frappe.get_meta(newdoc.doctype); + // set value + if (doc && doc.parentfield) { + //update values for child table + $.each(frappe._from_link.frm.fields_dict[doc.parentfield].grid.grid_rows, function (index, field) { + if (field.doc && field.doc.name === frappe._from_link.docname) { + if (meta.title_field && meta.show_title_field_in_link) { + frappe.add_link_title(newdoc.doctype, newdoc.name, newdoc[meta.title_field]); + } + frappe._from_link.set_value(newdoc.name); + } }); - } + } else { + if (meta.title_field && meta.show_title_field_in_link) { + frappe.add_link_title(newdoc.doctype, newdoc.name, newdoc[meta.title_field]); + } + frappe._from_link.set_value(newdoc.name); + } - frappe._from_link = null; + // refresh field + frappe._from_link.refresh(); + + // if from form, switch + if (frappe._from_link.frm) { + frappe.set_route("Form", + frappe._from_link.frm.doctype, frappe._from_link.frm.docname) + .then(() => { + frappe.utils.scroll_to(frappe._from_link_scrollY); + }); + } + + frappe._from_link = null; + }); } } diff --git a/frappe/public/js/frappe/link_title.js b/frappe/public/js/frappe/link_title.js new file mode 100644 index 0000000000..82bc6c149c --- /dev/null +++ b/frappe/public/js/frappe/link_title.js @@ -0,0 +1,34 @@ +// for link titles +frappe._link_titles = {}; + +frappe.get_link_title = function(doctype, name) { + if (!doctype || !name) { + return; + } + + return frappe._link_titles[doctype + "::" + name]; +}; + +frappe.add_link_title = function (doctype, name, value) { + if (!doctype || !name) { + return; + } + + frappe._link_titles[doctype + "::" + name] = value; +}; + +frappe.set_link_title = function(f) { + let doctype = f.get_options(); + let docname = f.get_input_value(); + + if ((!in_list(frappe.boot.doctypes_with_show_link_field_title, doctype)) || (!doctype || !docname) || + (frappe.get_link_title(doctype, docname))) { + return; + } + + frappe.xcall("frappe.desk.search.get_link_title", {"doctype": doctype, "docname": docname}).then((r) => { + if (r && docname !== r) { + f.set_input_label(r); + } + }); +}; \ No newline at end of file diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index 12fa9c8e21..9f09b288b6 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -241,6 +241,11 @@ frappe.request.call = function(opts) { $.extend(frappe._messages, data.__messages); } + // sync link titles + if (data._link_titles) { + $.extend(frappe._link_titles, data._link_titles); + } + // callbacks var status_code_handler = statusCode[xhr.statusCode().status]; if (status_code_handler) { diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index b2b2b623e2..d8bc760668 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -313,6 +313,10 @@ frappe.ui.Filter = class { return this.utils.get_selected_value(this.field, this.get_condition()); } + get_selected_label() { + return this.utils.get_selected_label(this.field); + } + get_condition() { return this.filter_edit_area.find('.condition').val(); } @@ -360,7 +364,7 @@ frappe.ui.Filter = class { get_filter_button_text() { let value = this.utils.get_formatted_value( this.field, - this.get_selected_value() + this.get_selected_label() || this.get_selected_value() ); return `${__(this.field.df.label)} ${__(this.get_condition())} ${__( value @@ -448,6 +452,12 @@ frappe.ui.filter_utils = { return val; }, + get_selected_label(field) { + if (in_list(["Link", "Dynamic Link"], field.df.fieldtype)) { + return field.get_label_value(); + } + }, + get_default_condition(df) { if (df.fieldtype == 'Data') { return 'like'; diff --git a/frappe/www/printview.py b/frappe/www/printview.py index cdf47790eb..268a01eafb 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -170,6 +170,38 @@ def get_rendered_template(doc, name=None, print_format=None, meta=None, return html +def set_link_titles(doc): + # Replaces name with title of link field doctype + + meta = frappe.get_meta(doc.doctype) + set_title_values_for_link_and_dynamic_link_fields(meta, doc) + set_title_values_for_table_and_multiselect_fields(meta, doc) + +def set_title_values_for_link_and_dynamic_link_fields(meta, doc): + for field in meta.get_link_fields() + meta.get_dynamic_link_fields(): + if not doc.get(field.fieldname): + continue + + # If link field, then get doctype from options + # If dynamic link field, then get doctype from dependent field + doctype = field.options if field.fieldtype == "Link" else doc.get(field.options) + + meta = frappe.get_meta(doctype) + if not meta or not (meta.title_field and meta.show_title_field_in_link): + continue + + link_title = frappe.get_cached_value(doctype, doc.get(field.fieldname), meta.title_field) + setattr(doc, field.fieldname, link_title) + +def set_title_values_for_table_and_multiselect_fields(meta, doc): + for field in meta.get_table_fields(): + if not doc.get(field.fieldname): + continue + + _meta = frappe.get_meta(field.options) + for value in doc.get(field.fieldname): + set_title_values_for_link_and_dynamic_link_fields(_meta, value) + def convert_markdown(doc, meta): '''Convert text field values to markdown if necessary''' for field in meta.fields: @@ -191,6 +223,7 @@ def get_html_and_style(doc, name=None, print_format=None, meta=None, doc = frappe.get_doc(json.loads(doc)) print_format = get_print_format_doc(print_format, meta=meta or frappe.get_meta(doc.doctype)) + set_link_titles(doc) try: html = get_rendered_template(doc, name=name, print_format=print_format, meta=meta,