Merge branch '1996-datalist_component' of verdnatura/salix into dev
gitea/salix/dev This commit looks good Details

This commit is contained in:
Joan Sanchez 2020-02-11 11:00:18 +00:00 committed by Gitea
commit e8ae2f5767
3 changed files with 332 additions and 0 deletions

View File

@ -0,0 +1,36 @@
<div class="container">
<div
ng-transclude="prepend"
class="prepend">
</div>
<div class="infix">
<div class="fix prefix"></div>
<div class="control"></div>
<div class="fix suffix"></div>
<label>
<span translate>{{::$ctrl.label}}</span>
<span class="required">*</span>
</label>
</div>
<div class="icons pre">
<vn-icon
icon="clear"
translate-attr="{title: 'Clear'}"
ng-click="$ctrl.onClear($event)">
</vn-icon>
<vn-icon
ng-if="::$ctrl.info != null"
icon="info_outline"
vn-tooltip="{{::$ctrl.info}}">
</vn-icon>
</div>
<div
ng-transclude="append"
class="append">
</div>
<div class="icons post"></div>
<div class="underline blur"></div>
<div class="underline focus"></div>
</div>
<div class="hint"></div>
<datalist vn-id="datalist" id="datalist-{{::$ctrl.name}}"></datalist>

View File

@ -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: '<?',
data: '<?',
url: '@?',
fields: '<?',
include: '<?',
where: '<?',
order: '@?',
limit: '<?'
},
transclude: {
tplItem: '?tplItem'
}
});

View File

@ -49,3 +49,4 @@ import './textarea';
import './th';
import './treeview';
import './wday-picker';
import './datalist';