From dbcc6410167ac8ca3f96c848fc2d50e16fc8eb38 Mon Sep 17 00:00:00 2001 From: Joan Sanchez Date: Tue, 11 Feb 2020 11:34:54 +0100 Subject: [PATCH] 1996 - Datalist component --- front/core/components/datalist/index.html | 36 +++ front/core/components/datalist/index.js | 295 ++++++++++++++++++++++ front/core/components/index.js | 1 + 3 files changed, 332 insertions(+) create mode 100644 front/core/components/datalist/index.html create mode 100644 front/core/components/datalist/index.js diff --git a/front/core/components/datalist/index.html b/front/core/components/datalist/index.html new file mode 100644 index 000000000..15c0f0e8a --- /dev/null +++ b/front/core/components/datalist/index.html @@ -0,0 +1,36 @@ +
+
+
+
+
+
+
+ +
+
+ + + + +
+
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/front/core/components/datalist/index.js b/front/core/components/datalist/index.js new file mode 100644 index 000000000..06fe9492e --- /dev/null +++ b/front/core/components/datalist/index.js @@ -0,0 +1,295 @@ +import ngModule from '../../module'; +import ArrayModel from '../array-model/array-model'; +import CrudModel from '../crud-model/crud-model'; +import {mergeWhere} from 'vn-loopback/util/filter'; +import Textfield from '../textfield/textfield'; + +export default class Datalist extends Textfield { + constructor($element, $scope, $compile, $transclude) { + super($element, $scope, $compile); + this.$transclude = $transclude; + this.searchDelay = 300; + this._selection = null; + + this.buildInput('text'); + + this.input.setAttribute('autocomplete', 'off'); + } + + get field() { + return super.field; + } + + set field(value) { + let oldValue = super.field; + super.field = value; + + value = value == '' || value == null ? null : value; + oldValue = oldValue == '' || oldValue == null ? null : oldValue; + + this.refreshSelection(); + + if (!value || value === oldValue && this.modelData != null) return; + + if (this.validSelection(value)) return; + + if (!oldValue) + return this.fetchSelection(); + + this.$timeout.cancel(this.searchTimeout); + + if (this.model) { + this.model.clear(); + if (!this.data) { + this.searchTimeout = this.$timeout(() => { + this.refreshModel(); + this.searchTimeout = null; + }, this.field != null ? this.searchDelay : 0); + } else + this.refreshModel(); + } + } + + validSelection(selection) { + return this.modelData && this.modelData.find(item => { + return item[this.valueField] == selection; + }); + } + + refreshSelection() { + const selectedItem = this.validSelection(this.field); + + if (this.field == null) { + this.selection = null; + return; + } + + if (selectedItem) + this.selection = selectedItem; + } + + get name() { + return super.name; + } + + set name(value) { + super.name = value; + + this.input.setAttribute('list', `datalist-${value}`); + } + + get modelData() { + return this.model ? this.model.data : null; + } + + get url() { + return this._url; + } + + set url(value) { + this._url = value; + if (value) { + this.model = new CrudModel(this.$q, this.$http); + this.model.autoLoad = false; + this.model.url = value; + this.model.$onInit(); + } + } + + get data() { + return this._data; + } + + set data(value) { + this._data = value; + if (value) { + this.model = new ArrayModel(this.$q, this.$filter); + this.model.autoLoad = false; + this.model.orgData = value; + this.model.$onInit(); + } + } + + /** + * @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; + } + + refreshModel() { + let model = this.model; + + let order; + if (this.order) + order = this.order; + else if (this.showField) + order = `${this.showField} ASC`; + + let filter = { + order, + limit: this.limit || 30 + }; + + if (model instanceof CrudModel) { + let searchExpr = this._field == null + ? null + : this.searchFunction({$search: this._field}); + + Object.assign(filter, { + fields: this.getFields(), + include: this.include, + where: mergeWhere(this.where, searchExpr) + }); + } else if (model instanceof ArrayModel) { + if (this._field != null) + filter.where = this.searchFunction({$search: this._field}); + } + + return this.model.applyFilter(filter).then(() => { + if (this.validSelection(this.field)) { + this.refreshSelection(); + this.destroyList(); + } else + this.buildList(); + + // this.emit('select', {selection}); + }); + } + + searchFunction(scope) { + if (this.model instanceof CrudModel) + return {[this.showField]: {like: `%${scope.$search}%`}}; + if (this.model instanceof ArrayModel) + return {[this.showField]: scope.$search}; + } + + fetchSelection() { + const data = this.modelData; + + if (data) { + let selection = data.find(i => this.validSelection(i[this.valueField])); + 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.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; + } + + getFields() { + const fields = []; + fields.push(this.valueField); + fields.push(this.showField); + + if (this.fields) { + for (let field of this.fields) + fields.push(field); + } + + return fields; + } + + buildList() { + const list = this.$.datalist; + const data = this.modelData; + + this.destroyList(); + + const hasTemplate = this.$transclude && this.$transclude.isSlotFilled('tplItem'); + const fragment = this.document.createDocumentFragment(); + + if (data) { + for (let item of data) { + const option = document.createElement('option'); + option.setAttribute('value', item[this.valueField]); + + if (hasTemplate) { + this.$transclude((clone, scope) => { + Object.assign(scope, item); + option.appendChild(clone[0]); + this.scopes.push(scope); + }, null, 'tplItem'); + } else { + const text = document.createTextNode(item[this.showField]); + option.appendChild(text); + } + + fragment.appendChild(option); + } + + list.appendChild(fragment); + } + } + + destroyList() { + const list = this.$.datalist; + if (list) + list.innerHTML = ''; + + if (this.scopes) { + for (let scope of this.scopes) + scope.$destroy(); + } + + this.scopes = []; + } +} + +Datalist.$inject = ['$element', '$scope', '$compile', '$transclude']; + +ngModule.vnComponent('vnDatalist', { + controller: Datalist, + template: require('./index.html'), + bindings: { + showField: '@?', + valueField: '@?', + selection: '=?', + multiple: '