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:
Carlos 2017-09-20 15:07:30 +02:00
commit 205f6b2c54
11 changed files with 392 additions and 431 deletions

View File

@ -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">

View File

@ -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"

View File

@ -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>

View File

@ -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 = [];
}
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;
}); });
}
}
}
componentHandler.upgradeElement($element[0].firstChild); get field() {
return this.multiple ? this._multiField : this._field;
} }
set field(value) { set field(value) {
this.locked = true; this.finding = true;
if (value && value.hasOwnProperty(this.valueField)) {
this._field = value[this.valueField];
this.setMultiField(value[this.valueField]);
} else {
this.setValue(value); this.setValue(value);
this.locked = false;
} }
get field() {
return this.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() { if (val === null) {
let mdlField = this.element.firstChild.MaterialTextfield; this._multiField = [];
if (mdlField)
mdlField.updateClasses_();
}
loadData(textFilter) {
textFilter = textFilter ? textFilter : '';
if (this.lastSearch === textFilter) {
this.showPopoverIfFocus();
return;
}
this.lastSearch = textFilter;
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 { } else {
let text = this.document.createTextNode(data[i][this.showField]); let index = this._multiField.indexOf(val);
li.appendChild(text); if (index === -1) {
} this._multiField.push(val);
}
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'); this._multiField.splice(index, 1);
popover.addEventListener('click',
e => this.onPopoverClick(e));
popover.addEventListener('mousedown',
e => this.onPopoverMousedown(e));
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) {
for (let i = 0; i < childs.length; i++) this.showItem(data[i]);
if (childs[i] === target) {
this.selectPopoverOption(i);
break;
}
}
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; return;
} }
event.preventDefault(); this.requestItem(value);
} else {
this._field = null;
this.setMultiField(null);
this.displayValue = '';
} }
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); requestItem(value) {
this.timeoutId = null; if (!value) return;
}
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;
let childs = popover.childNodes;
let len = this.displayData.length;
if (this.activeOption >= 0)
childs[this.activeOption].className = '';
if (index >= len)
index = 0;
else if (index < 0)
index = len - 1;
if (index >= 0) {
let opt = childs[index];
let top = popover.scrollTop;
let height = popover.clientHeight;
if (opt.offsetTop + opt.offsetHeight > top + height)
top = opt.offsetTop + opt.offsetHeight - height;
else if (opt.offsetTop < top)
top = opt.offsetTop;
opt.className = 'active';
popover.scrollTop = top;
}
this.activeOption = index;
}
setValue(value) {
this.value = value;
if (value) {
let data = this.data;
if (data)
for (let i = 0; i < data.length; i++)
if (data[i][this.valueField] == value) {
this.putItem(data[i]);
return;
}
this.requestItem();
} else
this.putItem(null);
}
selectOptionByIndex(index) {
this.selectOptionByDataIndex(this.data, index);
}
selectOptionByDataIndex(data, index) {
if (data && index >= 0 && index < data.length)
this.putItem(data[index]);
else
this.putItem(null);
}
putItem(item) {
this.showItem(item);
let value = item ? item[this.valueField] : undefined;
if (!this.locked)
this.value = value;
if (this.onChange)
this.onChange({item: item});
}
showItem(item) { showItem(item) {
this.input.value = item ? item[this.showField] : ''; this.displayValue = item ? item[this.showField] : '';
this.item = item; this.field = item;
this.mdlUpdate(); this.setMultiField(item);
} }
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;
}
findItems(search) {
if (!this.url)
return this.items ? this.items : [];
if (search && !this.finding) {
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';
}
let json = JSON.stringify(filter);
this.$http.get(`${this.url}?filter=${json}`).then(
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;
this.$element.bind('mouseover', e => {
this.$timeout(() => {
this.mouseFocus = true;
this.showDropDown = this.focused;
});
});
this.$element.bind('mouseout', () => {
this.$timeout(() => {
this.mouseFocus = false;
this.showDropDown = this.focused;
});
});
this.$element.bind('focusin', e => {
this.$timeout(() => {
this.focused = true;
this.showDropDown = true;
});
});
this.$element.bind('focusout', e => {
this.$timeout(() => {
this.focused = false;
this.showDropDown = this.mouseFocus;
});
});
let rectangle = this.$element[0].getBoundingClientRect();
this.width = Math.round(rectangle.width) - 10;
}
$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
}); });

View File

@ -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);
});
});
});

View File

@ -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;
}
} }

View File

@ -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>

View File

@ -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;
if (this.filterAction) {
this.filterAction({search: this.search}); 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});
} else {
this.filterItems();
} }
selectOption() {
if (this.activeOption >= 0 && this.activeOption < this.items.length && this.items[this.activeOption]) {
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'
} }
}); });

View File

@ -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;
} }

View File

@ -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>

View File

@ -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'];