Merge branch 'dev' of https://git.verdnatura.es/salix into dev
* 'dev' of https://git.verdnatura.es/salix: autocomplete-v2 renamed removed autocomplete folder quitado autocomplete anterior autocomplete con seleccion multiple transclude funcionando autocomplete recatoring 80% refactoring 30%
This commit is contained in:
commit
205f6b2c54
|
@ -22,7 +22,7 @@
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-textfield vn-one label="Email" field="$ctrl.client.email" info="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget rhoncus metus. Nullam quis ante venenatis, laoreet tellus ac, egestas tellus. Suspendisse placerat tristique libero a posuere."></vn-textfield>
|
<vn-textfield vn-one label="Email" field="$ctrl.client.email" info="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget rhoncus metus. Nullam quis ante venenatis, laoreet tellus ac, egestas tellus. Suspendisse placerat tristique libero a posuere."></vn-textfield>
|
||||||
<vn-autocomplete vn-one
|
<vn-autocomplete vn-one
|
||||||
initial-value="$ctrl.client.salesPerson"
|
initial-data="$ctrl.client.salesPerson"
|
||||||
field="$ctrl.client.salesPersonFk"
|
field="$ctrl.client.salesPersonFk"
|
||||||
url="/client/api/Employees"
|
url="/client/api/Employees"
|
||||||
show-field="name"
|
show-field="name"
|
||||||
|
@ -30,11 +30,11 @@
|
||||||
select-fields="surname"
|
select-fields="surname"
|
||||||
label="Salesperson">
|
label="Salesperson">
|
||||||
<tpl-item>
|
<tpl-item>
|
||||||
{{::i.name}} {{::i.surname}}
|
{{$parent.$parent.item.name}} {{$parent.$parent.item.surname}}
|
||||||
</tpl-item>
|
</tpl-item>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
<vn-autocomplete vn-one
|
<vn-autocomplete vn-one
|
||||||
initial-value="$ctrl.client.contactChannel"
|
initial-data="$ctrl.client.contactChannel"
|
||||||
field="$ctrl.client.contactChannelFk"
|
field="$ctrl.client.contactChannelFk"
|
||||||
url="/client/api/ContactChannels"
|
url="/client/api/ContactChannels"
|
||||||
label="Channel">
|
label="Channel">
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-textfield vn-one label="Código postal" field="$ctrl.client.postcode"></vn-textfield>
|
<vn-textfield vn-one label="Código postal" field="$ctrl.client.postcode"></vn-textfield>
|
||||||
<vn-autocomplete vn-one
|
<vn-autocomplete vn-one
|
||||||
initial-value="$ctrl.client.province"
|
initial-data="$ctrl.client.province"
|
||||||
field="$ctrl.client.provinceFk"
|
field="$ctrl.client.provinceFk"
|
||||||
url="/client/api/Provinces"
|
url="/client/api/Provinces"
|
||||||
show-field="name"
|
show-field="name"
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
label="Provincia">
|
label="Provincia">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
<vn-autocomplete vn-one
|
<vn-autocomplete vn-one
|
||||||
initial-value="$ctrl.client.country"
|
initial-data="$ctrl.client.country"
|
||||||
field="$ctrl.client.countryFk"
|
field="$ctrl.client.countryFk"
|
||||||
url="/client/api/Countries"
|
url="/client/api/Countries"
|
||||||
show-field="name"
|
show-field="name"
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
|
<vn-vertical ng-click="$ctrl.showDropDown = true">
|
||||||
<input type="text"
|
<vn-textfield vn-one label="{{$ctrl.label}}" model="$ctrl.displayValue"></vn-textfield>
|
||||||
class="mdl-textfield__input"
|
<vn-drop-down vn-one
|
||||||
ng-keydown="$ctrl.onKeydown($event)"
|
items="$ctrl.items"
|
||||||
ng-click="$ctrl.onClick($event)"
|
show="$ctrl.showDropDown"
|
||||||
ng-keyup="$ctrl.onKeyup($event)"
|
selected="$ctrl.field"
|
||||||
ng-focus="$ctrl.onFocus($event)"
|
filter="true"
|
||||||
ng-blur="$ctrl.onBlur($event)"/>
|
load-more="$ctrl.getItems()"
|
||||||
<button
|
show-load-more="$ctrl.maxRow"
|
||||||
type="button"
|
filter-action="$ctrl.findItems(search)"
|
||||||
class="mdl-chip__action ng-hide"
|
item-width="$ctrl.width"
|
||||||
tabindex="-1"
|
multiple="$ctrl.multiple"
|
||||||
translate-attr="{title: 'Clear'}"
|
><vn-item ng-transclude="tplItem">{{$parent.item.name}}</vn-item></vn-drop-down>
|
||||||
ng-show="$ctrl.value"
|
</vn-vertical>
|
||||||
ng-click="$ctrl.onClear()"
|
|
||||||
>
|
|
||||||
<i class="material-icons">clear</i>
|
|
||||||
</button>
|
|
||||||
<label class="mdl-textfield__label">{{$ctrl.label | translate}}</label>
|
|
||||||
</div>
|
|
|
@ -2,320 +2,136 @@ import {module} from '../module';
|
||||||
import Component from '../lib/component';
|
import Component from '../lib/component';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
/**
|
class Autocomplete extends Component {
|
||||||
* Combobox like component with search and partial data loading features.
|
constructor($element, $scope, $http, $timeout) {
|
||||||
*/
|
|
||||||
export default class Autocomplete extends Component {
|
|
||||||
constructor($element, $scope, $http, vnPopover, $transclude, $timeout) {
|
|
||||||
super($element);
|
super($element);
|
||||||
this.input = $element[0].querySelector('input');
|
this.$element = $element;
|
||||||
this.item = null;
|
|
||||||
this.$timeout = $timeout;
|
|
||||||
// this.data = null;
|
|
||||||
this.popover = null;
|
|
||||||
this.popoverId = null;
|
|
||||||
this.displayData = null;
|
|
||||||
this.timeoutId = null;
|
|
||||||
this.lastSearch = null;
|
|
||||||
this.lastRequest = null;
|
|
||||||
this.currentRequest = null;
|
|
||||||
this.moreData = false;
|
|
||||||
this.activeOption = -1;
|
|
||||||
this.locked = false;
|
|
||||||
this.$http = $http;
|
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
this.vnPopover = vnPopover;
|
this.$http = $http;
|
||||||
this.$transclude = $transclude;
|
this.$timeout = $timeout;
|
||||||
this.scopes = null;
|
|
||||||
|
|
||||||
Object.assign(this, {
|
this._showDropDown = false;
|
||||||
maxRows: 10,
|
this.finding = false;
|
||||||
requestDelay: 350,
|
this.findMore = false;
|
||||||
showField: 'name',
|
this._value = null;
|
||||||
valueField: 'id',
|
this._field = null;
|
||||||
itemAs: 'i'
|
this._preLoad = false;
|
||||||
});
|
this.maxRow = 10;
|
||||||
|
this.showField = this.showField || 'name';
|
||||||
|
this.valueField = this.valueField || 'id';
|
||||||
|
this.items = this.data || [];
|
||||||
|
this.displayValueMultiCheck = [];
|
||||||
|
this._multiField = [];
|
||||||
|
}
|
||||||
|
|
||||||
componentHandler.upgradeElement($element[0].firstChild);
|
get showDropDown() {
|
||||||
|
return this._showDropDown;
|
||||||
|
}
|
||||||
|
set showDropDown(value) {
|
||||||
|
if (value && this.url && !this._preLoad) {
|
||||||
|
this._preLoad = true;
|
||||||
|
this.getItems();
|
||||||
|
}
|
||||||
|
this._showDropDown = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayValue() {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set displayValue(value) {
|
||||||
|
let val = (value === undefined || value === '') ? null : value;
|
||||||
|
if (this.multiple && val) {
|
||||||
|
let index = this.displayValueMultiCheck.indexOf(val);
|
||||||
|
if (index === -1)
|
||||||
|
this.displayValueMultiCheck.push(val);
|
||||||
|
else
|
||||||
|
this.displayValueMultiCheck.splice(index, 1);
|
||||||
|
|
||||||
|
this._value = this.displayValueMultiCheck.join(', ');
|
||||||
|
} else {
|
||||||
|
this._value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === null) {
|
||||||
|
this.field = null;
|
||||||
|
if (this.multiple && this.items.length) {
|
||||||
|
this.displayValueMultiCheck = [];
|
||||||
|
this.items.map(item => {
|
||||||
|
item.checked = false;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get field() {
|
||||||
|
return this.multiple ? this._multiField : this._field;
|
||||||
}
|
}
|
||||||
set field(value) {
|
set field(value) {
|
||||||
this.locked = true;
|
this.finding = true;
|
||||||
this.setValue(value);
|
if (value && value.hasOwnProperty(this.valueField)) {
|
||||||
this.locked = false;
|
this._field = value[this.valueField];
|
||||||
}
|
this.setMultiField(value[this.valueField]);
|
||||||
get field() {
|
} else {
|
||||||
return this.value;
|
this.setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && value.hasOwnProperty(this.showField))
|
||||||
|
this.displayValue = value[this.showField];
|
||||||
|
|
||||||
|
this.finding = false;
|
||||||
|
|
||||||
|
if (this.onChange)
|
||||||
|
this.onChange({item: this._field});
|
||||||
}
|
}
|
||||||
|
|
||||||
set initialData(value) {
|
set initialData(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
if (!this.data)
|
this.field = value;
|
||||||
this.data = [];
|
|
||||||
this.data.push(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set selectFields(value) {
|
|
||||||
this._selectFields = [];
|
|
||||||
|
|
||||||
if (!value)
|
setMultiField(val) {
|
||||||
return;
|
if (val && typeof val === 'object' && val[this.valueField]) {
|
||||||
|
val = val[this.valueField];
|
||||||
let res = value.split(',');
|
|
||||||
for (let i of res)
|
|
||||||
this._selectFields.push(i.trim());
|
|
||||||
}
|
|
||||||
mdlUpdate() {
|
|
||||||
let mdlField = this.element.firstChild.MaterialTextfield;
|
|
||||||
if (mdlField)
|
|
||||||
mdlField.updateClasses_();
|
|
||||||
}
|
|
||||||
loadData(textFilter) {
|
|
||||||
textFilter = textFilter ? textFilter : '';
|
|
||||||
|
|
||||||
if (this.lastSearch === textFilter) {
|
|
||||||
this.showPopoverIfFocus();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
if (val === null) {
|
||||||
this.lastSearch = textFilter;
|
this._multiField = [];
|
||||||
|
|
||||||
let lastRequest = this.lastRequest;
|
|
||||||
let requestWillSame = lastRequest !== null
|
|
||||||
&& !this.moreData
|
|
||||||
&& textFilter.substr(0, lastRequest.length) === lastRequest;
|
|
||||||
|
|
||||||
if (requestWillSame || !this.url)
|
|
||||||
this.localFilter(textFilter);
|
|
||||||
else if (this.url)
|
|
||||||
this.requestData(textFilter, false);
|
|
||||||
else
|
|
||||||
this.setDisplayData(this.data);
|
|
||||||
}
|
|
||||||
getRequestFields() {
|
|
||||||
let fields = {};
|
|
||||||
fields[this.valueField] = true;
|
|
||||||
fields[this.showField] = true;
|
|
||||||
|
|
||||||
if (this._selectFields)
|
|
||||||
for (let field of this._selectFields)
|
|
||||||
fields[field] = true;
|
|
||||||
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
requestData(textFilter, append) {
|
|
||||||
let where = {};
|
|
||||||
let skip = 0;
|
|
||||||
|
|
||||||
if (textFilter)
|
|
||||||
where[this.showField] = {regexp: textFilter};
|
|
||||||
if (append && this.data)
|
|
||||||
skip = this.data.length;
|
|
||||||
|
|
||||||
let filter = {
|
|
||||||
fields: this.getRequestFields(),
|
|
||||||
where: where,
|
|
||||||
order: `${this.showField} ASC`,
|
|
||||||
skip: skip,
|
|
||||||
limit: this.maxRows
|
|
||||||
};
|
|
||||||
|
|
||||||
this.lastRequest = textFilter ? textFilter : '';
|
|
||||||
let json = JSON.stringify(filter);
|
|
||||||
|
|
||||||
if (this.currentRequest)
|
|
||||||
this.currentRequest.resolve();
|
|
||||||
|
|
||||||
this.currentRequest = this.$http.get(`${this.url}?filter=${json}`);
|
|
||||||
this.currentRequest.then(
|
|
||||||
json => this.onRequest(json.data, append),
|
|
||||||
json => this.onRequest([])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
onRequest(data, append) {
|
|
||||||
this.currentRequest = null;
|
|
||||||
this.moreData = data.length >= this.maxRows;
|
|
||||||
|
|
||||||
if (!append || !this.data)
|
|
||||||
this.data = data;
|
|
||||||
else
|
|
||||||
this.data = this.data.concat(data);
|
|
||||||
|
|
||||||
this.setDisplayData(this.data);
|
|
||||||
}
|
|
||||||
localFilter(textFilter) {
|
|
||||||
let regex = new RegExp(textFilter, 'i');
|
|
||||||
let data = this.data.filter(item => {
|
|
||||||
return regex.test(item[this.showField]);
|
|
||||||
});
|
|
||||||
this.setDisplayData(data);
|
|
||||||
}
|
|
||||||
setDisplayData(data) {
|
|
||||||
this.displayData = data;
|
|
||||||
this.showPopoverIfFocus();
|
|
||||||
}
|
|
||||||
showPopoverIfFocus() {
|
|
||||||
if (this.hasFocus)
|
|
||||||
this.showPopover();
|
|
||||||
}
|
|
||||||
destroyScopes() {
|
|
||||||
if (this.scopes)
|
|
||||||
for (let scope of this.scopes)
|
|
||||||
scope.$destroy();
|
|
||||||
}
|
|
||||||
showPopover() {
|
|
||||||
let data = this.displayData;
|
|
||||||
|
|
||||||
if (!data)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let fragment = this.document.createDocumentFragment();
|
|
||||||
this.destroyScopes();
|
|
||||||
this.scopes = [];
|
|
||||||
|
|
||||||
let hasTemplate = this.$transclude.isSlotFilled('tplItem');
|
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
let li = this.document.createElement('li');
|
|
||||||
fragment.appendChild(li);
|
|
||||||
|
|
||||||
if (hasTemplate) {
|
|
||||||
this.$transclude((clone, scope) => {
|
|
||||||
scope[this.itemAs] = data[i];
|
|
||||||
li.appendChild(clone[0]);
|
|
||||||
this.scopes[i] = scope;
|
|
||||||
}, null, 'tplItem');
|
|
||||||
} else {
|
|
||||||
let text = this.document.createTextNode(data[i][this.showField]);
|
|
||||||
li.appendChild(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.moreData) {
|
|
||||||
let li = this.document.createElement('li');
|
|
||||||
li.appendChild(this.document.createTextNode('Load more'));
|
|
||||||
li.className = 'load-more';
|
|
||||||
fragment.appendChild(li);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.popover) {
|
|
||||||
this.popover.innerHTML = '';
|
|
||||||
this.popover.appendChild(fragment);
|
|
||||||
} else {
|
} else {
|
||||||
let popover = this.document.createElement('ul');
|
let index = this._multiField.indexOf(val);
|
||||||
popover.addEventListener('click',
|
if (index === -1) {
|
||||||
e => this.onPopoverClick(e));
|
this._multiField.push(val);
|
||||||
popover.addEventListener('mousedown',
|
} else {
|
||||||
e => this.onPopoverMousedown(e));
|
this._multiField.splice(index, 1);
|
||||||
popover.className = 'vn-autocomplete';
|
}
|
||||||
popover.appendChild(fragment);
|
|
||||||
this.popoverId = this.vnPopover.show(popover, this.input);
|
|
||||||
this.popover = popover;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hidePopover() {
|
|
||||||
if (!this.popover) return;
|
|
||||||
this.activeOption = -1;
|
|
||||||
this.vnPopover.hide(this.popoverId);
|
|
||||||
this.destroyScopes();
|
|
||||||
this.popover = null;
|
|
||||||
}
|
|
||||||
selectPopoverOption(index) {
|
|
||||||
if (!this.popover || index === -1) return;
|
|
||||||
if (index < this.displayData.length) {
|
|
||||||
this.selectOptionByDataIndex(this.displayData, index);
|
|
||||||
this.hidePopover();
|
|
||||||
} else
|
|
||||||
this.requestData(this.lastRequest, true);
|
|
||||||
}
|
|
||||||
onPopoverClick(event) {
|
|
||||||
let target = event.target;
|
|
||||||
let childs = this.popover.childNodes;
|
|
||||||
|
|
||||||
if (target === this.popover)
|
setValue(value) {
|
||||||
return;
|
if (value) {
|
||||||
|
let data = this.items;
|
||||||
|
|
||||||
while (target.parentNode !== this.popover)
|
if (data && data.length)
|
||||||
target = target.parentNode;
|
for (let i = 0; i < data.length; i++)
|
||||||
|
if (data[i][this.valueField] === value) {
|
||||||
|
this.showItem(data[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < childs.length; i++)
|
this.requestItem(value);
|
||||||
if (childs[i] === target) {
|
} else {
|
||||||
this.selectPopoverOption(i);
|
this._field = null;
|
||||||
break;
|
this.setMultiField(null);
|
||||||
}
|
this.displayValue = '';
|
||||||
}
|
|
||||||
onPopoverMousedown(event) {
|
|
||||||
// Prevents input from loosing focus
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
onClear() {
|
|
||||||
this.setValue(null);
|
|
||||||
this.$timeout(
|
|
||||||
() => {
|
|
||||||
this.mdlUpdate();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
onClick(event) {
|
|
||||||
if (!this.popover)
|
|
||||||
this.showPopover();
|
|
||||||
}
|
|
||||||
onFocus() {
|
|
||||||
this.hasFocus = true;
|
|
||||||
this.input.select();
|
|
||||||
|
|
||||||
this.loadData();
|
|
||||||
}
|
|
||||||
onBlur() {
|
|
||||||
this.hasFocus = false;
|
|
||||||
this.restoreShowValue();
|
|
||||||
this.hidePopover();
|
|
||||||
}
|
|
||||||
onKeydown(event) {
|
|
||||||
switch (event.keyCode) {
|
|
||||||
case 13: // Enter
|
|
||||||
this.selectPopoverOption(this.activeOption);
|
|
||||||
break;
|
|
||||||
case 27: // Escape
|
|
||||||
this.restoreShowValue();
|
|
||||||
this.input.select();
|
|
||||||
break;
|
|
||||||
case 38: // Arrow up
|
|
||||||
this.activateOption(this.activeOption - 1);
|
|
||||||
break;
|
|
||||||
case 40: // Arrow down
|
|
||||||
this.activateOption(this.activeOption + 1);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
requestItem(value) {
|
||||||
}
|
if (!value) return;
|
||||||
onKeyup(event) {
|
|
||||||
if (!this.isKeycodePrintable(event.keyCode)) return;
|
|
||||||
if (this.timeoutId) clearTimeout(this.timeoutId);
|
|
||||||
this.timeoutId = setTimeout(() => this.onTimeout(), this.requestDelay);
|
|
||||||
}
|
|
||||||
onTimeout() {
|
|
||||||
this.loadData(this.input.value);
|
|
||||||
this.timeoutId = null;
|
|
||||||
}
|
|
||||||
isKeycodePrintable(keyCode) {
|
|
||||||
return keyCode === 32 // Spacebar
|
|
||||||
|| keyCode === 8 // Backspace
|
|
||||||
|| (keyCode > 47 && keyCode < 58) // Numbers
|
|
||||||
|| (keyCode > 64 && keyCode < 91) // Letters
|
|
||||||
|| (keyCode > 95 && keyCode < 112) // Numpad
|
|
||||||
|| (keyCode > 185 && keyCode < 193) // ;=,-./`
|
|
||||||
|| (keyCode > 218 && keyCode < 223); // [\]'
|
|
||||||
}
|
|
||||||
restoreShowValue() {
|
|
||||||
this.putItem(this.item);
|
|
||||||
}
|
|
||||||
requestItem() {
|
|
||||||
if (!this.value) return;
|
|
||||||
|
|
||||||
let where = {};
|
let where = {};
|
||||||
where[this.valueField] = this.value;
|
where[this.valueField] = value;
|
||||||
|
|
||||||
let filter = {
|
let filter = {
|
||||||
fields: this.getRequestFields(),
|
fields: this.getRequestFields(),
|
||||||
|
@ -335,87 +151,135 @@ export default class Autocomplete extends Component {
|
||||||
else
|
else
|
||||||
this.showItem(null);
|
this.showItem(null);
|
||||||
}
|
}
|
||||||
activateOption(index) {
|
|
||||||
if (!this.popover)
|
|
||||||
this.showPopover();
|
|
||||||
|
|
||||||
let popover = this.popover;
|
showItem(item) {
|
||||||
let childs = popover.childNodes;
|
this.displayValue = item ? item[this.showField] : '';
|
||||||
let len = this.displayData.length;
|
this.field = item;
|
||||||
|
this.setMultiField(item);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.activeOption >= 0)
|
getRequestFields() {
|
||||||
childs[this.activeOption].className = '';
|
let fields = {};
|
||||||
|
fields[this.valueField] = true;
|
||||||
|
fields[this.showField] = true;
|
||||||
|
|
||||||
if (index >= len)
|
if (this._selectFields)
|
||||||
index = 0;
|
for (let field of this._selectFields)
|
||||||
else if (index < 0)
|
fields[field] = true;
|
||||||
index = len - 1;
|
|
||||||
|
|
||||||
if (index >= 0) {
|
return fields;
|
||||||
let opt = childs[index];
|
}
|
||||||
let top = popover.scrollTop;
|
|
||||||
let height = popover.clientHeight;
|
|
||||||
|
|
||||||
if (opt.offsetTop + opt.offsetHeight > top + height)
|
findItems(search) {
|
||||||
top = opt.offsetTop + opt.offsetHeight - height;
|
if (!this.url)
|
||||||
else if (opt.offsetTop < top)
|
return this.items ? this.items : [];
|
||||||
top = opt.offsetTop;
|
|
||||||
|
|
||||||
opt.className = 'active';
|
if (search && !this.finding) {
|
||||||
popover.scrollTop = top;
|
this.maxRow = false;
|
||||||
|
let filter = {where: {name: {regexp: search}}};
|
||||||
|
let json = JSON.stringify(filter);
|
||||||
|
this.finding = true;
|
||||||
|
this.$http.get(`${this.url}?filter=${json}`).then(
|
||||||
|
json => {
|
||||||
|
this.items = [];
|
||||||
|
json.data.forEach(
|
||||||
|
el => {
|
||||||
|
if (this.multiple) {
|
||||||
|
el.checked = this.field.indexOf(el[this.valueField]) !== -1;
|
||||||
|
}
|
||||||
|
this.items.push(el);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.finding = false;
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.finding = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (!search && !this.finding) {
|
||||||
|
this.maxRow = 10;
|
||||||
|
this.items = [];
|
||||||
|
this.getItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getItems() {
|
||||||
|
let filter = {};
|
||||||
|
|
||||||
|
if (this.maxRow) {
|
||||||
|
if (this.items) {
|
||||||
|
filter.skip = this.items.length;
|
||||||
|
}
|
||||||
|
filter.limit = this.maxRow;
|
||||||
|
filter.order = 'name ASC';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeOption = index;
|
let json = JSON.stringify(filter);
|
||||||
}
|
|
||||||
setValue(value) {
|
|
||||||
this.value = value;
|
|
||||||
|
|
||||||
if (value) {
|
this.$http.get(`${this.url}?filter=${json}`).then(
|
||||||
let data = this.data;
|
json => {
|
||||||
|
if (json.data.length)
|
||||||
|
json.data.forEach(
|
||||||
|
el => {
|
||||||
|
if (this.multiple) {
|
||||||
|
el.checked = this.field.indexOf(el[this.valueField]) !== -1;
|
||||||
|
}
|
||||||
|
this.items.push(el);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
else
|
||||||
|
this.maxRow = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$onInit() {
|
||||||
|
this.findMore = this.url && this.maxRow;
|
||||||
|
this.mouseFocus = false;
|
||||||
|
this.focused = false;
|
||||||
|
|
||||||
if (data)
|
this.$element.bind('mouseover', e => {
|
||||||
for (let i = 0; i < data.length; i++)
|
this.$timeout(() => {
|
||||||
if (data[i][this.valueField] == value) {
|
this.mouseFocus = true;
|
||||||
this.putItem(data[i]);
|
this.showDropDown = this.focused;
|
||||||
return;
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
this.requestItem();
|
this.$element.bind('mouseout', () => {
|
||||||
} else
|
this.$timeout(() => {
|
||||||
this.putItem(null);
|
this.mouseFocus = false;
|
||||||
}
|
this.showDropDown = this.focused;
|
||||||
selectOptionByIndex(index) {
|
});
|
||||||
this.selectOptionByDataIndex(this.data, index);
|
});
|
||||||
}
|
this.$element.bind('focusin', e => {
|
||||||
selectOptionByDataIndex(data, index) {
|
this.$timeout(() => {
|
||||||
if (data && index >= 0 && index < data.length)
|
this.focused = true;
|
||||||
this.putItem(data[index]);
|
this.showDropDown = true;
|
||||||
else
|
});
|
||||||
this.putItem(null);
|
});
|
||||||
}
|
this.$element.bind('focusout', e => {
|
||||||
putItem(item) {
|
this.$timeout(() => {
|
||||||
this.showItem(item);
|
this.focused = false;
|
||||||
let value = item ? item[this.valueField] : undefined;
|
this.showDropDown = this.mouseFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (!this.locked)
|
let rectangle = this.$element[0].getBoundingClientRect();
|
||||||
this.value = value;
|
this.width = Math.round(rectangle.width) - 10;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.onChange)
|
|
||||||
this.onChange({item: item});
|
|
||||||
}
|
|
||||||
showItem(item) {
|
|
||||||
this.input.value = item ? item[this.showField] : '';
|
|
||||||
this.item = item;
|
|
||||||
this.mdlUpdate();
|
|
||||||
}
|
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
this.destroyScopes();
|
this.$element.unbind('mouseover');
|
||||||
|
this.$element.unbind('mouseout');
|
||||||
|
this.$element.unbind('focusin');
|
||||||
|
this.$element.unbind('focusout');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Autocomplete.$inject = ['$element', '$scope', '$http', 'vnPopover', '$transclude', '$timeout'];
|
|
||||||
|
Autocomplete.$inject = ['$element', '$scope', '$http', '$timeout'];
|
||||||
|
|
||||||
module.component('vnAutocomplete', {
|
module.component('vnAutocomplete', {
|
||||||
template: require('./autocomplete.html'),
|
template: require('./autocomplete.html'),
|
||||||
|
controller: Autocomplete,
|
||||||
bindings: {
|
bindings: {
|
||||||
url: '@?',
|
url: '@?',
|
||||||
showField: '@?',
|
showField: '@?',
|
||||||
|
@ -426,10 +290,11 @@ module.component('vnAutocomplete', {
|
||||||
data: '<?',
|
data: '<?',
|
||||||
itemAs: '@?',
|
itemAs: '@?',
|
||||||
field: '=',
|
field: '=',
|
||||||
label: '@'
|
label: '@',
|
||||||
|
itemTemplate: '@?',
|
||||||
|
multiple: '@?'
|
||||||
},
|
},
|
||||||
transclude: {
|
transclude: {
|
||||||
tplItem: '?tplItem'
|
tplItem: '?tplItem'
|
||||||
},
|
}
|
||||||
controller: Autocomplete
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import './autocomplete.js';
|
|
||||||
|
|
||||||
describe('Core', () => {
|
|
||||||
describe('Component mdlUpdate', () => {
|
|
||||||
let $componentController;
|
|
||||||
let $state;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
angular.mock.module('client');
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(angular.mock.inject((_$componentController_, _$state_) => {
|
|
||||||
$componentController = _$componentController_;
|
|
||||||
$state = _$state_;
|
|
||||||
$state.params.id = '1234';
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should define and set address property', () => {
|
|
||||||
let controller = $componentController('vnAddressCreate', {$state: $state});
|
|
||||||
|
|
||||||
expect(controller.address.clientFk).toBe(1234);
|
|
||||||
expect(controller.address.enabled).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -34,4 +34,10 @@ vn-autocomplete {
|
||||||
.material-icons {
|
.material-icons {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
vn-drop-down{
|
||||||
|
margin-top: 47px;
|
||||||
|
}
|
||||||
|
vn-drop-down .dropdown-body .filter vn-icon {
|
||||||
|
margin-left: -26px;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,19 +1,22 @@
|
||||||
<vn-vertical class="dropdown-body" ng-show="$ctrl.show">
|
<vn-vertical class="dropdown-body" ng-show="$ctrl.show">
|
||||||
<vn-one ng-show="$ctrl.filter" class="filter">
|
<vn-one ng-show="$ctrl.filter" class="filter">
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<div ng-if="$ctrl.filterAction">
|
<input vn-one placeholder="{{'Search' | translate}}" type="text" ng-model="$ctrl.search"/>
|
||||||
<input vn-one placeholder="{{'Search' | translate}}" type="text" ng-model="$ctrl.search" ng-change="$ctrl.onFilterRest()"/>
|
|
||||||
</div>
|
|
||||||
<div ng-if="!$ctrl.filterAction">
|
|
||||||
<input vn-one placeholder="{{'Search' | translate}}" type="text" ng-model="$ctrl.search" ng-change="$ctrl.filterItems()"/>
|
|
||||||
</div>
|
|
||||||
<vn-icon vn-none icon="clear" ng-click="$ctrl.clearSearch()"></vn-icon>
|
<vn-icon vn-none icon="clear" ng-click="$ctrl.clearSearch()"></vn-icon>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
<vn-one>
|
<vn-one>
|
||||||
<ul class="dropdown">
|
<ul class="dropdown">
|
||||||
<li ng-repeat="item in $ctrl.itemsFiltered" ng-click="$ctrl.selected = item">{{::item.name}}</li>
|
<li tabIndex="-1"
|
||||||
<li ng-if="$ctrl.showLoadMore" class="dropdown__loadMore" ng-click="$ctrl.loadMore()" translate="Load More"></li>
|
ng-repeat="item in $ctrl.itemsFiltered track by $index"
|
||||||
|
ng-click="$ctrl.selectItem(item)"
|
||||||
|
ng-class="{'active': $index === $ctrl.activeOption, 'checked': item.checked}"
|
||||||
|
ng-mouseover="$ctrl.activeOption = $index"
|
||||||
|
>
|
||||||
|
<input type="checkbox" ng-checked="item.checked" ng-if="$ctrl.multiple">
|
||||||
|
<div ng-transclude="vnItem">{{item.name}}</div>
|
||||||
|
</li>
|
||||||
|
<li ng-if="$ctrl.showLoadMore" class="dropdown__loadMore" tabIndex="-1" ng-class="{'active': $ctrl.itemsFiltered.length === $ctrl.activeOption}" ng-click="$ctrl.loadMore();$ctrl.show = true;" translate="Load More"></li>
|
||||||
</ul>
|
</ul>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
</vn-vertical>
|
</vn-vertical>
|
|
@ -2,12 +2,41 @@ import {module} from '../module';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
export default class DropDown {
|
export default class DropDown {
|
||||||
constructor($element, $filter) {
|
constructor($element, $filter, $timeout) {
|
||||||
this.$element = $element;
|
this.$element = $element;
|
||||||
this.$filter = $filter;
|
this.$filter = $filter;
|
||||||
this.search = '';
|
this.$timeout = $timeout;
|
||||||
this.itemsFiltered = [];
|
|
||||||
|
|
||||||
|
this.parent = this.parent || $element[0].parentNode;
|
||||||
|
this._search = null;
|
||||||
|
this.itemsFiltered = [];
|
||||||
|
this._activeOption = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get search() {
|
||||||
|
return this._search;
|
||||||
|
}
|
||||||
|
set search(value) {
|
||||||
|
let val = (value === undefined && value === '') ? null : value;
|
||||||
|
this._search = val;
|
||||||
|
|
||||||
|
if (this.filterAction)
|
||||||
|
this.onFilterRest();
|
||||||
|
else
|
||||||
|
this.filterItems();
|
||||||
|
}
|
||||||
|
get activeOption() {
|
||||||
|
return this._activeOption;
|
||||||
|
}
|
||||||
|
set activeOption(value) {
|
||||||
|
if (value < 0) {
|
||||||
|
value = 0;
|
||||||
|
} else if (value >= this.items.length) {
|
||||||
|
value = this.showLoadMore ? this.items.length : this.items.length - 1;
|
||||||
|
}
|
||||||
|
this.$timeout(() => {
|
||||||
|
this._activeOption = value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
filterItems() {
|
filterItems() {
|
||||||
|
@ -15,32 +44,92 @@ export default class DropDown {
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterRest() {
|
onFilterRest() {
|
||||||
this.showLoadMore = false;
|
this.filterAction({search: this.search});
|
||||||
if (this.filterAction) {
|
|
||||||
this.filterAction({search: this.search});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$onChanges(changesObj) {
|
$onChanges(changesObj) {
|
||||||
if (changesObj.show && changesObj.top && changesObj.top.currentValue) {
|
if (changesObj.show && changesObj.top && changesObj.top.currentValue) {
|
||||||
this.$element.css('top', changesObj.top.currentValue + 'px');
|
this.$element.css('top', changesObj.top.currentValue + 'px');
|
||||||
}
|
}
|
||||||
|
if (changesObj.show && changesObj.itemWidth && changesObj.itemWidth.currentValue) {
|
||||||
|
this.$element.css('width', changesObj.itemWidth.currentValue + 'px');
|
||||||
|
}
|
||||||
if (changesObj.items) {
|
if (changesObj.items) {
|
||||||
this.filterItems();
|
this.filterItems();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSearch() {
|
clearSearch() {
|
||||||
this.search = '';
|
this.search = null;
|
||||||
this.showLoadMore = this.loadMore != null;
|
}
|
||||||
if (this.filterAction) {
|
|
||||||
this.filterAction({search: this.search});
|
selectOption() {
|
||||||
} else {
|
if (this.activeOption >= 0 && this.activeOption < this.items.length && this.items[this.activeOption]) {
|
||||||
this.filterItems();
|
this.selected = this.items[this.activeOption];
|
||||||
|
this.show = false;
|
||||||
|
this.clearSearch();
|
||||||
|
} else if (this.showLoadMore && this.activeOption === this.items.length) {
|
||||||
|
this.loadMore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onKeydown(event) {
|
||||||
|
if (this.show) {
|
||||||
|
switch (event.keyCode) {
|
||||||
|
case 13: // Enter
|
||||||
|
this.$timeout(() => {
|
||||||
|
this.selectOption();
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
case 27: // Escape
|
||||||
|
this.clearSearch();
|
||||||
|
break;
|
||||||
|
case 38: // Arrow up
|
||||||
|
this.activeOption--;
|
||||||
|
this.$timeout(() => {
|
||||||
|
this.setScrollPosition();
|
||||||
|
}, 100);
|
||||||
|
break;
|
||||||
|
case 40: // Arrow down
|
||||||
|
this.activeOption++;
|
||||||
|
this.$timeout(() => {
|
||||||
|
this.setScrollPosition();
|
||||||
|
}, 100);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setScrollPosition() {
|
||||||
|
let dropdown = this.$element[0].querySelector('ul.dropdown');
|
||||||
|
let child = dropdown ? dropdown.childNodes[this.activeOption] : null;
|
||||||
|
if (child && typeof child.scrollIntoView === 'function') {
|
||||||
|
child.scrollIntoView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectItem(item) {
|
||||||
|
this.selected = item;
|
||||||
|
if (this.multiple) {
|
||||||
|
item.checked = !item.checked;
|
||||||
|
this.show = true;
|
||||||
|
} else {
|
||||||
|
this.show = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
if (this.parent)
|
||||||
|
this.parent.addEventListener('keydown', e => this.onKeydown(e));
|
||||||
|
}
|
||||||
|
$onDestroy() {
|
||||||
|
if (this.parent)
|
||||||
|
this.parent.removeEventListener('keydown', e => this.onKeydown(e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DropDown.$inject = ['$element', '$filter'];
|
DropDown.$inject = ['$element', '$filter', '$timeout'];
|
||||||
|
|
||||||
module.component('vnDropDown', {
|
module.component('vnDropDown', {
|
||||||
template: require('./drop-down.html'),
|
template: require('./drop-down.html'),
|
||||||
|
@ -50,9 +139,16 @@ module.component('vnDropDown', {
|
||||||
show: '<',
|
show: '<',
|
||||||
filter: '@?',
|
filter: '@?',
|
||||||
selected: '=',
|
selected: '=',
|
||||||
|
search: '=?',
|
||||||
loadMore: '&?',
|
loadMore: '&?',
|
||||||
filterAction: '&?',
|
filterAction: '&?',
|
||||||
showLoadMore: '<?',
|
showLoadMore: '=?',
|
||||||
top: '<?'
|
top: '<?',
|
||||||
|
itemWidth: '<?',
|
||||||
|
parent: '<?',
|
||||||
|
multiple: '<?'
|
||||||
|
},
|
||||||
|
transclude: {
|
||||||
|
vnItem: '?vnItem'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,6 +30,7 @@ vn-drop-down {
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
li {
|
li {
|
||||||
|
outline: none;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -38,8 +39,12 @@ vn-drop-down {
|
||||||
color: rgb(255,171,64);
|
color: rgb(255,171,64);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
input[type=checkbox]{
|
||||||
|
float: left;
|
||||||
|
margin: 5px 5px 0 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
li:hover{
|
li.active{
|
||||||
background-color: #3D3A3B;
|
background-color: #3D3A3B;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
show="$ctrl.showDropDown"
|
show="$ctrl.showDropDown"
|
||||||
selected="$ctrl.selected"
|
selected="$ctrl.selected"
|
||||||
filter="true"
|
filter="true"
|
||||||
|
parent="$ctrl.element"
|
||||||
></vn-drop-down>
|
></vn-drop-down>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="$ctrl.findMore">
|
<div ng-if="$ctrl.findMore">
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
load-more="$ctrl.getItems()"
|
load-more="$ctrl.getItems()"
|
||||||
show-load-more="$ctrl.maxRow"
|
show-load-more="$ctrl.maxRow"
|
||||||
filter-action="$ctrl.findItems(search)"
|
filter-action="$ctrl.findItems(search)"
|
||||||
|
parent="$ctrl.element"
|
||||||
></vn-drop-down>
|
></vn-drop-down>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -9,6 +9,7 @@ export default class IconMenu {
|
||||||
this._showDropDown = false;
|
this._showDropDown = false;
|
||||||
this.finding = false;
|
this.finding = false;
|
||||||
this.findMore = false;
|
this.findMore = false;
|
||||||
|
this.element = $element[0];
|
||||||
}
|
}
|
||||||
get showDropDown() {
|
get showDropDown() {
|
||||||
return this._showDropDown;
|
return this._showDropDown;
|
||||||
|
@ -34,6 +35,7 @@ export default class IconMenu {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else if (!search && !this.finding) {
|
} else if (!search && !this.finding) {
|
||||||
|
this.maxRow = 10;
|
||||||
this.items = [];
|
this.items = [];
|
||||||
this.getItems();
|
this.getItems();
|
||||||
}
|
}
|
||||||
|
@ -83,10 +85,23 @@ export default class IconMenu {
|
||||||
this.showDropDown = false;
|
this.showDropDown = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
this.$element.bind('focusin', e => {
|
||||||
|
this.$timeout(() => {
|
||||||
|
this.showDropDown = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.$element.bind('focusout', e => {
|
||||||
|
this.$timeout(() => {
|
||||||
|
this.showDropDown = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
this.$element.unbind('mouseover');
|
this.$element.unbind('mouseover');
|
||||||
this.$element.unbind('mouseout');
|
this.$element.unbind('mouseout');
|
||||||
|
this.$element.unbind('focusin');
|
||||||
|
this.$element.unbind('focusout');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IconMenu.$inject = ['$element', '$http', '$timeout'];
|
IconMenu.$inject = ['$element', '$http', '$timeout'];
|
||||||
|
|
Loading…
Reference in New Issue