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 Wether 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, $, $compile, $transclude) { super($element, $, $compile); 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 }; let json = encodeURIComponent(JSON.stringify(filter)); this.$http.get(`${this.url}?filter=${json}`).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 = ''; let hasTemplate = this.$transclude && this.$transclude.isSlotFilled('tplItem'); 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]; if (hasTemplate) { let template = this.$transclude(() => {}, null, 'tplItem').text(); display = this.$interpolate(template)(this._selection); } } } 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', '$compile', '$transclude']; ngModule.vnComponent('vnAutocomplete', { template: require('./index.html'), controller: Autocomplete, bindings: { showField: '@?', valueField: '@?', initialData: '