Merge branch '1996-datalist_component' of verdnatura/salix into dev
gitea/salix/dev This commit looks good
Details
gitea/salix/dev This commit looks good
Details
This commit is contained in:
commit
e8ae2f5767
|
@ -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>
|
|
@ -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'
|
||||
}
|
||||
});
|
|
@ -49,3 +49,4 @@ import './textarea';
|
|||
import './th';
|
||||
import './treeview';
|
||||
import './wday-picker';
|
||||
import './datalist';
|
||||
|
|
Loading…
Reference in New Issue