1996 - Datalist component
This commit is contained in:
parent
3a662782d6
commit
dbcc641016
|
@ -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 './th';
|
||||||
import './treeview';
|
import './treeview';
|
||||||
import './wday-picker';
|
import './wday-picker';
|
||||||
|
import './datalist';
|
||||||
|
|
Loading…
Reference in New Issue