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,