diff --git a/frappe/public/build.json b/frappe/public/build.json index 73a9fae189..f1b5420f3e 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -93,7 +93,8 @@ "public/js/frappe/form/controls/geolocation.js", "public/js/frappe/form/controls/multiselect.js", "public/js/frappe/form/controls/multicheck.js", - "public/js/frappe/form/controls/table_multiselect.js" + "public/js/frappe/form/controls/table_multiselect.js", + "public/js/frappe/form/controls/multiselect_pills.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", diff --git a/frappe/public/js/frappe/form/controls/multiselect.js b/frappe/public/js/frappe/form/controls/multiselect.js index 93bcd2ca15..64ca4fc83d 100644 --- a/frappe/public/js/frappe/form/controls/multiselect.js +++ b/frappe/public/js/frappe/form/controls/multiselect.js @@ -1,93 +1,6 @@ import Awesomplete from 'awesomplete'; frappe.ui.form.ControlMultiSelect = frappe.ui.form.ControlAutocomplete.extend({ - make_input() { - this._super(); - this.$input_area = $(this.input_area); - this.$input_area.addClass('form-control table-multiselect'); - this.$input.removeClass('form-control'); - - this.$input.on("awesomplete-selectcomplete", () => { - this.$input.val('').focus(); - }); - - // used as an internal model to store values - this.rows = []; - - this.$input_area.on('click', '.btn-remove', (e) => { - const $target = $(e.currentTarget); - const $value = $target.closest('.tb-selected-value'); - - const value = decodeURIComponent($value.data().value); - this.rows = this.rows.filter(val => val !== value); - - this.parse_validate_and_set_in_model(''); - }); - - this.$input.on('keydown', e => { - // if backspace key pressed on empty input, delete last value - if (e.keyCode == frappe.ui.keyCode.BACKSPACE && e.target.value === '') { - this.rows = this.rows.slice(0, this.rows.length - 1); - this.parse_validate_and_set_in_model(''); - } - }); - }, - - parse(value) { - if (value) { - this.rows.push(value); - } - - return this.rows; - }, - - validate(value) { - const rows = (value || []).slice(); - - if (rows.length === 0) { - return rows; - } - - const all_rows_except_last = rows.slice(0, rows.length - 1); - const last_value = rows[rows.length - 1]; - - // falsy value - if (!last_value) { - return all_rows_except_last; - } - - // duplicate value - if (all_rows_except_last.includes(last_value)) { - return all_rows_except_last; - } - - return rows; - }, - - set_formatted_input(value) { - this.rows = value || []; - this.set_pill_html(this.rows); - }, - - set_pill_html(values) { - const html = values - .map(value => this.get_pill_html(value)) - .join(''); - - this.$input_area.find('.tb-selected-value').remove(); - this.$input_area.prepend(html); - }, - - get_pill_html(value) { - const encoded_value = encodeURIComponent(value); - return `
- - -
`; - }, - get_awesomplete_settings() { const settings = this._super(); @@ -110,37 +23,59 @@ frappe.ui.form.ControlMultiSelect = frappe.ui.form.ControlAutocomplete.extend({ } return v; + }, + + replace: function(text) { + const before = this.input.value.match(/^.+,\s*|/)[0]; + this.input.value = before + text + ", "; } }); }, get_value() { - return this.rows; + let data = this._super(); + // find value of label from option list and return actual value string + if (this.df.options && this.df.options.length && this.df.options[0].label) { + data = data.split(',').map(op => op.trim()); + data = data.map(val => { + let option = this.df.options.find(op => op.label === val); + return option ? option.value : null; + }).filter(n => n != null).join(', '); + } + return data; + }, + + set_formatted_input(value) { + if (!value) return; + // find label of value from option list and set from it as input + if (this.df.options && this.df.options.length && this.df.options[0].label) { + value = value.split(',').map(d => d.trim()).map(val => { + let option = this.df.options.find(op => op.value === val); + return option ? option.label : val; + }).filter(n => n != null).join(', '); + } + this._super(value); }, get_values() { - return this.rows; + const value = this.get_value() || ''; + const values = value.split(/\s*,\s*/).filter(d => d); + + return values; }, get_data() { let data; if(this.df.get_data) { data = this.df.get_data(); - if (data && data.then) { - data.then((r) => { - this.set_data(r); - }); - data = this.get_value(); - } else { - this.set_data(data); - } + this.set_data(data); } else { data = this._super(); } const values = this.get_values() || []; // return values which are not already selected - if (data) data.filter(d => !values.includes(d)); + if(data) data.filter(d => !values.includes(d)); return data; } }); diff --git a/frappe/public/js/frappe/form/controls/multiselect_pills.js b/frappe/public/js/frappe/form/controls/multiselect_pills.js new file mode 100644 index 0000000000..8796c95eaa --- /dev/null +++ b/frappe/public/js/frappe/form/controls/multiselect_pills.js @@ -0,0 +1,150 @@ +import Awesomplete from 'awesomplete'; + +frappe.ui.form.ControlMultiSelectPills = frappe.ui.form.ControlAutocomplete.extend({ + make_input() { + this._super(); + this.$input_area = $(this.input_area); + this.$multiselect_wrapper = $('
') + .addClass('form-control table-multiselect') + .appendTo(this.$input_area); + + this.$input.removeClass('form-control'); + this.$input_area.find('.awesomplete').appendTo(this.$multiselect_wrapper); + + this.$input.on("awesomplete-selectcomplete", () => { + this.$input.val('').focus(); + }); + + // used as an internal model to store values + this.rows = []; + + this.$input_area.on('click', '.btn-remove', (e) => { + const $target = $(e.currentTarget); + const $value = $target.closest('.tb-selected-value'); + + const value = decodeURIComponent($value.data().value); + this.rows = this.rows.filter(val => val !== value); + + this.parse_validate_and_set_in_model(''); + }); + + this.$input.on('keydown', e => { + // if backspace key pressed on empty input, delete last value + if (e.keyCode == frappe.ui.keyCode.BACKSPACE && e.target.value === '') { + this.rows = this.rows.slice(0, this.rows.length - 1); + this.parse_validate_and_set_in_model(''); + } + }); + }, + + parse(value) { + if (value) { + this.rows.push(value); + } + + return this.rows; + }, + + validate(value) { + const rows = (value || []).slice(); + + if (rows.length === 0) { + return rows; + } + + const all_rows_except_last = rows.slice(0, rows.length - 1); + const last_value = rows[rows.length - 1]; + + // falsy value + if (!last_value) { + return all_rows_except_last; + } + + // duplicate value + if (all_rows_except_last.includes(last_value)) { + return all_rows_except_last; + } + + return rows; + }, + + set_formatted_input(value) { + this.rows = value || []; + this.set_pill_html(this.rows); + }, + + set_pill_html(values) { + const html = values + .map(value => this.get_pill_html(value)) + .join(''); + + this.$multiselect_wrapper.find('.tb-selected-value').remove(); + this.$multiselect_wrapper.prepend(html); + }, + + get_pill_html(value) { + const encoded_value = encodeURIComponent(value); + return `
+ + +
`; + }, + + get_awesomplete_settings() { + const settings = this._super(); + + return Object.assign(settings, { + filter: function(text, input) { + let d = this.get_item(text.value); + if(!d) { + return Awesomplete.FILTER_CONTAINS(text, input.match(/[^,]*$/)[0]); + } + + let getMatch = value => Awesomplete.FILTER_CONTAINS(value, input.match(/[^,]*$/)[0]); + + // match typed input with label or value or description + let v = getMatch(d.label); + if(!v && d.value) { + v = getMatch(d.value); + } + if(!v && d.description) { + v = getMatch(d.description); + } + + return v; + } + }); + }, + + get_value() { + return this.rows; + }, + + get_values() { + return this.rows; + }, + + get_data() { + let data; + if(this.df.get_data) { + data = this.df.get_data(); + if (data && data.then) { + data.then((r) => { + this.set_data(r); + }); + data = this.get_value(); + } else { + this.set_data(data); + } + } else { + data = this._super(); + } + const values = this.get_values() || []; + + // return values which are not already selected + if (data) data.filter(d => !values.includes(d)); + return data; + } +}); diff --git a/frappe/public/js/frappe/views/components/DeskSection.vue b/frappe/public/js/frappe/views/components/DeskSection.vue index 3c5dcd9af0..d7504c7d76 100644 --- a/frappe/public/js/frappe/views/components/DeskSection.vue +++ b/frappe/public/js/frappe/views/components/DeskSection.vue @@ -32,7 +32,7 @@ export default { { label: __('Shortcuts'), fieldname: 'links', - fieldtype: 'MultiSelect', + fieldtype: 'MultiSelectPills', get_data() { return frappe.call('frappe.desk.moduleview.get_links', { app: module.app,