fix: Revert Multiselect, add MultiSelectPills
MultiSelect with pills doesn't work well in Report filters. So we introduce another control which can be used wherever appropriate.
This commit is contained in:
parent
a0e7f28438
commit
f7408d4d1c
4 changed files with 186 additions and 100 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 `<div class="btn-group tb-selected-value" data-value="${encoded_value}">
|
||||
<button class="btn btn-default btn-xs btn-link-to-form">${__(value)}</button>
|
||||
<button class="btn btn-default btn-xs btn-remove">
|
||||
<i class="fa fa-remove text-muted"></i>
|
||||
</button>
|
||||
</div>`;
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
150
frappe/public/js/frappe/form/controls/multiselect_pills.js
Normal file
150
frappe/public/js/frappe/form/controls/multiselect_pills.js
Normal file
|
|
@ -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 = $('<div>')
|
||||
.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 `<div class="btn-group tb-selected-value" data-value="${encoded_value}">
|
||||
<button class="btn btn-default btn-xs btn-link-to-form">${__(value)}</button>
|
||||
<button class="btn btn-default btn-xs btn-remove">
|
||||
<i class="fa fa-remove text-muted"></i>
|
||||
</button>
|
||||
</div>`;
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue