salix/front/core/components/autocomplete/index.js

319 lines
7.9 KiB
JavaScript
Executable File

import ngModule from '../../module';
import Field from '../field';
import assignProps from '../../lib/assign-props';
import {mergeWhere} from 'vn-loopback/util/filter';
import './style.scss';
/**
* Input with option selector.
*
* @property {String} showFiled The data field name that should be shown
* @property {String} valueField The data field name that should be used as value
* @property {Array} data Static data for the autocomplete
* @property {Object} intialData An initial data to avoid the server request used to get the selection
* @property {Boolean} multiple Whether to allow multiple selection
* @property {Object} selection Current object selected
*
* @event change Thrown when value is changed
*/
export default class Autocomplete extends Field {
constructor($element, $, $transclude) {
super($element, $, $transclude);
this.$transclude = $transclude;
this._selection = null;
this.input = this.element.querySelector('input');
}
$postLink() {
super.$postLink();
this.assignDropdownProps();
this.showField = this.$.dropDown.showField;
this.valueField = this.$.dropDown.valueField;
this.refreshSelection();
}
/**
* @type {any} The autocomplete value.
*/
get field() {
return super.field;
}
set field(value) {
super.field = value;
this.refreshSelection();
}
get model() {
return this._model;
}
set model(value) {
this.dropDownAssign({model: value});
}
get data() {
return this._data;
}
set data(value) {
this.dropDownAssign({data: value});
}
get url() {
return this._url;
}
set url(value) {
this.dropDownAssign({url: value});
}
dropDownAssign(props) {
for (let prop in props)
this[`_${prop}`] = props[prop];
if (this.$.dropDown)
Object.assign(this.$.dropDown, props);
}
/**
* @type {Object} The selected data object, you can use this property
* to prevent requests to display the initial value.
*/
get selection() {
return this._selection;
}
set selection(value) {
this._selection = value;
this.refreshDisplayed();
}
get initialData() {
return this._initialData;
}
set initialData(value) {
this._initialData = value;
this.refreshSelection();
}
selectionIsValid(selection) {
return selection
&& selection[this.valueField] == this._field
&& selection[this.showField] != null;
}
refreshSelection() {
if (this._field == null) {
this.selection = null;
return;
}
if (!(this.valueField && this.showField)
|| this.selectionIsValid(this._selection))
return;
const selection = this.fetchSelection();
this.selection = selection;
}
fetchSelection() {
if (this.selectionIsValid(this.initialData))
return this.initialData;
if (!this.$.dropDown)
return null;
let data;
if (this.$.dropDown.model)
data = this.$.dropDown.model.orgData;
if (data) {
let selection = data.find(i => this.selectionIsValid(i));
if (selection) return selection;
}
if (this.url) {
let where = {};
if (this.multiple)
where[this.valueField] = {inq: this.field};
else
where[this.valueField] = this._field;
where = mergeWhere(where, this.fetchFunction);
let filter = {
fields: this.$.dropDown.getFields(),
where: where
};
if (this.include)
filter.include = this.include;
let json = encodeURIComponent(JSON.stringify(filter));
let url;
if (this.url.includes('?'))
url = `${this.url}&filter=${json}`;
else
url = `${this.url}?filter=${json}`;
this.$http.get(url).then(
json => this.onSelectionRequest(json.data),
() => this.onSelectionRequest()
);
}
return null;
}
onSelectionRequest(data) {
if (data && data.length > 0) {
if (this.multiple)
this.selection = data;
else
this.selection = data[0];
} else
this.selection = null;
}
refreshDisplayed() {
let display = '';
if (this._selection && this.showField) {
if (this.multiple && Array.isArray(this._selection)) {
for (let item of this._selection) {
if (display.length > 0) display += ', ';
display += item[this.showField];
}
} else
display = this._selection[this.showField];
}
this.input.value = display;
if (this.translateFields && this.selection) {
const translations = [];
for (let field of this.translateFields) {
const fieldValue = this._selection[field];
translations.push({
original: fieldValue,
value: this.$t(fieldValue)
});
}
for (let translation of translations) {
const orgValue = translation.original;
const value = translation.value;
display = display.replace(orgValue, value);
}
this.input.value = display;
}
}
onDropDownSelect(item) {
const value = item[this.valueField];
this.selection = item;
this.change(value);
}
onDropDownClose() {
setTimeout(() => this.focus());
}
onContainerKeyDown(event) {
if (event.defaultPrevented) return;
switch (event.key) {
case 'ArrowUp':
case 'ArrowDown':
this.showDropDown();
break;
default:
if (event.key.length == 1)
this.showDropDown(event.key);
else
return;
}
}
onContainerClick(event) {
if (event.defaultPrevented) return;
this.showDropDown();
}
onDataReady() {
this.refreshSelection();
}
assignDropdownProps() {
if (!this.$.dropDown) return;
this.$.dropDown.copySlot('tplItem', this.$transclude);
assignProps(this, this.$.dropDown, [
'valueField',
'showField',
'showFilter',
'multiple',
'translateFields',
'model',
'data',
'url',
'fields',
'include',
'where',
'order',
'limit',
'searchFunction',
'whereFunction'
]);
}
showDropDown(search) {
if (!this.editable) return;
this.assignDropdownProps();
this.$.dropDown.show(this.container, search);
}
get fetchFunction() {
return this._fetchFunction;
}
set fetchFunction(value) {
this._fetchFunction = value;
if (value)
this.refreshSelection();
}
}
Autocomplete.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('vnAutocomplete', {
template: require('./index.html'),
controller: Autocomplete,
bindings: {
showField: '@?',
valueField: '@?',
initialData: '<?',
showFilter: '<?',
selection: '=?',
multiple: '<?',
data: '<?',
url: '@?',
fields: '<?',
include: '<?',
where: '<?',
order: '@?',
limit: '<?',
translateFields: '<?',
searchFunction: '&?',
whereFunction: '&?',
fetchFunction: '<?'
},
transclude: {
tplItem: '?tplItem'
}
});