salix/client/core/src/components/drop-down/drop-down.js

383 lines
9.6 KiB
JavaScript
Raw Normal View History

2018-02-10 15:18:01 +00:00
import ngModule from '../../module';
2018-03-09 13:15:30 +00:00
import Component from '../../lib/component';
import './style.scss';
2018-03-09 13:15:30 +00:00
import './model';
2018-03-09 13:15:30 +00:00
export default class DropDown extends Component {
constructor($element, $scope, $transclude, $timeout, $http) {
2018-03-09 13:15:30 +00:00
super($element, $scope);
this.$transclude = $transclude;
2017-09-14 11:40:55 +00:00
this.$timeout = $timeout;
2018-03-09 13:15:30 +00:00
this.valueField = 'id';
this.showField = 'name';
this._search = undefined;
2017-09-14 11:40:55 +00:00
this._activeOption = -1;
2018-03-09 13:15:30 +00:00
this.showLoadMore = true;
this.showFilter = true;
this.docKeyDownHandler = e => this.onDocKeyDown(e);
}
$postLink() {
2018-03-09 13:15:30 +00:00
this.input = this.element.querySelector('.search');
this.ul = this.element.querySelector('ul');
2018-03-09 13:15:30 +00:00
this.list = this.element.querySelector('.list');
this.list.addEventListener('scroll', e => this.onScroll(e));
}
2018-03-09 13:15:30 +00:00
get shown() {
return this.$.popover.shown;
}
2018-03-09 13:15:30 +00:00
set shown(value) {
this.$.popover.shown = value;
}
2017-09-14 11:40:55 +00:00
get search() {
return this._search;
}
2017-09-14 11:40:55 +00:00
set search(value) {
2018-03-09 13:15:30 +00:00
value = value == '' || value == null ? null : value;
if (value === this._search) return;
2017-09-14 11:40:55 +00:00
2018-03-09 13:15:30 +00:00
this._search = value;
this.$.model.clear();
if (value != null)
this._activeOption = 0;
2018-03-09 13:15:30 +00:00
this.$timeout.cancel(this.searchTimeout);
this.searchTimeout = this.$timeout(() => {
this.refreshModel();
this.searchTimeout = null;
}, 350);
this.buildList();
}
get statusText() {
let model = this.$.model;
let data = model.data;
let statusText = null;
if (model.isLoading || this.searchTimeout)
statusText = 'Loading...';
else if (data == null)
statusText = 'No data';
else if (model.moreRows)
statusText = 'Load More';
else if (data.length === 0)
statusText = 'No results found';
return statusText;
}
get model() {
return this.$.model;
2017-09-14 11:40:55 +00:00
}
2017-09-14 11:40:55 +00:00
get activeOption() {
return this._activeOption;
}
2018-03-09 13:15:30 +00:00
/**
* Shows the drop-down. If a parent is specified it is shown in a visible
* relative position to it.
*
* @param {String} search The initial search term or %null
*/
show(search) {
this._activeOption = -1;
this.search = search;
2018-03-09 13:15:30 +00:00
this.buildList();
this.$.popover.parent = this.parent;
this.$.popover.show();
}
2018-03-09 13:15:30 +00:00
/**
* Hides the drop-down.
*/
hide() {
this.$.popover.hide();
2017-11-22 12:10:33 +00:00
}
2018-03-09 13:15:30 +00:00
/**
* Activates an option and scrolls the drop-down to that option.
2018-03-09 13:15:30 +00:00
*
* @param {Number} option The option index
*/
moveToOption(option) {
this.activateOption(option);
let list = this.list;
let li = this.activeLi;
if (!li) return;
let liRect = li.getBoundingClientRect();
let listRect = list.getBoundingClientRect();
if (liRect.bottom > listRect.bottom)
list.scrollTop += liRect.bottom - listRect.bottom;
else if (liRect.top < listRect.top)
list.scrollTop -= listRect.top - liRect.top;
}
2018-03-09 13:15:30 +00:00
/**
* Activates an option.
2018-03-09 13:15:30 +00:00
*
* @param {Number} option The option index
*/
activateOption(option) {
this._activeOption = option;
if (this.activeLi)
this.activeLi.className = '';
2018-03-09 13:15:30 +00:00
let data = this.$.model.data;
if (option >= 0 && data && option < data.length) {
this.activeLi = this.ul.children[option];
2018-03-09 13:15:30 +00:00
this.activeLi.className = 'active';
}
}
2017-11-22 12:10:33 +00:00
2018-03-09 13:15:30 +00:00
/**
* Selects an option.
*
* @param {Number} option The option index
*/
selectOption(option) {
let data = this.$.model.data;
let item = option != -1 && data ? data[option] : null;
if (item) {
let value = item[this.valueField];
2017-11-22 12:10:33 +00:00
2018-03-09 13:15:30 +00:00
if (this.multiple) {
if (!Array.isArray(this.selection)) {
this.selection = [];
this.field = [];
}
this.selection.push(item);
this.field.push(value);
} else {
this.selection = item;
this.field = value;
2017-11-22 12:10:33 +00:00
}
2018-03-09 13:15:30 +00:00
if (this.onSelect)
this.onSelect({value: value});
2017-11-22 12:10:33 +00:00
}
2018-03-09 13:15:30 +00:00
if (!this.multiple)
this.$.popover.hide();
2017-11-22 12:10:33 +00:00
}
2018-03-09 13:15:30 +00:00
refreshModel() {
this.$.model.filter = {
fields: this.selectFields,
where: this.getWhere(this._search),
order: this.getOrder(),
limit: this.limit || 8
};
this.$.model.refresh(this._search);
2017-06-29 06:13:30 +00:00
}
2018-03-09 13:15:30 +00:00
getWhere(search) {
if (search == '' || search == null)
return undefined;
if (this.where) {
let jsonFilter = this.where.replace(/search/g, search);
return this.$.$eval(jsonFilter);
}
2018-03-09 13:15:30 +00:00
let where = {};
where[this.showField] = {regexp: search};
return where;
2017-06-29 06:13:30 +00:00
}
2018-03-09 13:15:30 +00:00
getOrder() {
if (this.order)
return this.order;
else if (this.showField)
return `${this.showField} ASC`;
return undefined;
2017-09-14 11:40:55 +00:00
}
onOpen() {
this.document.addEventListener('keydown', this.docKeyDownHandler);
this.list.scrollTop = 0;
this.input.focus();
2017-09-20 09:50:53 +00:00
}
2018-03-09 13:15:30 +00:00
onClose() {
this.document.removeEventListener('keydown', this.docKeyDownHandler);
2017-09-20 11:52:53 +00:00
}
2018-03-09 13:15:30 +00:00
onClearClick() {
this.search = null;
}
2018-03-09 13:15:30 +00:00
onScroll() {
let list = this.list;
let shouldLoad =
list.scrollTop + list.clientHeight >= (list.scrollHeight - 40)
&& !this.$.model.isLoading;
if (shouldLoad)
this.$.model.loadMore();
2017-11-22 12:10:33 +00:00
}
2018-03-09 13:15:30 +00:00
onLoadMoreClick(event) {
event.preventDefault();
this.$.model.loadMore();
}
onContainerClick(event) {
if (event.defaultPrevented) return;
let index = getPosition(this.ul, event);
2018-03-09 13:15:30 +00:00
if (index != -1) this.selectOption(index);
}
onModelDataChange() {
this.buildList();
}
onDocKeyDown(event) {
if (event.defaultPrevented) return;
let data = this.$.model.data;
let option = this.activeOption;
let nOpts = data ? data.length - 1 : 0;
switch (event.keyCode) {
case 9: // Tab
this.selectOption(option);
return;
2018-03-09 13:15:30 +00:00
case 13: // Enter
this.selectOption(option);
break;
case 38: // Up
this.moveToOption(option <= 0 ? nOpts : option - 1);
break;
case 40: // Down
this.moveToOption(option >= nOpts ? 0 : option + 1);
break;
case 35: // End
this.moveToOption(nOpts);
break;
case 36: // Start
this.moveToOption(0);
break;
default:
return;
2017-11-22 12:10:33 +00:00
}
2018-03-09 13:15:30 +00:00
event.preventDefault();
this.$.$applyAsync();
}
buildList() {
this.destroyList();
2018-03-09 13:15:30 +00:00
let hasTemplate = this.$transclude &&
this.$transclude.isSlotFilled('tplItem');
let fragment = this.document.createDocumentFragment();
let data = this.$.model.data;
if (data) {
for (let i = 0; i < data.length; i++) {
let li = this.document.createElement('li');
fragment.appendChild(li);
if (this.multiple) {
let check = this.document.createElement('input');
check.type = 'checkbox';
li.appendChild(check);
if (this.field && this.field.indexOf(data[i][this.valueField]) != -1)
check.checked = true;
}
if (hasTemplate) {
this.$transclude((clone, scope) => {
Object.assign(scope, 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);
}
}
2017-11-22 12:10:33 +00:00
}
2018-03-09 13:15:30 +00:00
this.ul.appendChild(fragment);
2018-03-09 13:15:30 +00:00
this.activateOption(this._activeOption);
2018-03-13 12:21:53 +00:00
this.$.$applyAsync(() => this.$.popover.relocate());
2017-11-22 12:10:33 +00:00
}
2018-03-09 13:15:30 +00:00
destroyList() {
this.ul.innerHTML = '';
2018-03-09 13:15:30 +00:00
if (this.scopes)
for (let scope of this.scopes)
scope.$destroy();
this.scopes = [];
2017-09-14 11:40:55 +00:00
}
2018-03-09 13:15:30 +00:00
2017-09-14 11:40:55 +00:00
$onDestroy() {
this.destroyList();
2017-09-14 11:40:55 +00:00
}
}
DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$http'];
2018-03-09 13:15:30 +00:00
/**
* Gets the position of an event element relative to a parent.
*
* @param {HTMLElement} parent The parent element
* @param {Event} event The event object
* @return {Number} The element index
*/
function getPosition(parent, event) {
let target = event.target;
let children = parent.children;
if (target === parent)
return -1;
while (target.parentNode !== parent)
target = target.parentNode;
for (let i = 0; i < children.length; i++)
if (children[i] === target)
return i;
return -1;
}
2018-02-10 15:18:01 +00:00
ngModule.component('vnDropDown', {
template: require('./drop-down.html'),
controller: DropDown,
bindings: {
2018-03-09 13:15:30 +00:00
field: '=?',
data: '<?',
selection: '=?',
search: '<?',
limit: '<?',
showFilter: '<?',
2017-09-20 11:52:53 +00:00
parent: '<?',
2018-03-09 13:15:30 +00:00
multiple: '<?',
onSelect: '&?'
2017-09-20 09:50:53 +00:00
},
transclude: {
2018-03-09 13:15:30 +00:00
tplItem: '?tplItem'
2017-06-21 11:16:37 +00:00
}
});