pull/978/head
Cagatay Civici 2021-02-15 15:35:42 +03:00
parent 7b6e8e2e68
commit 75b2ff8561
1 changed files with 180 additions and 53 deletions

View File

@ -7,9 +7,7 @@
<input v-if="editable" type="text" class="p-dropdown-label p-inputtext" :disabled="disabled" @focus="onFocus" @blur="onBlur" :placeholder="placeholder" :value="editableInputValue" @input="onEditableInput" <input v-if="editable" type="text" class="p-dropdown-label p-inputtext" :disabled="disabled" @focus="onFocus" @blur="onBlur" :placeholder="placeholder" :value="editableInputValue" @input="onEditableInput"
aria-haspopup="listbox" :aria-expanded="overlayVisible"> aria-haspopup="listbox" :aria-expanded="overlayVisible">
<span v-if="!editable" :class="labelClass"> <span v-if="!editable" :class="labelClass">
<slot name="value" :value="modelValue" :placeholder="placeholder"> <slot name="value" :value="modelValue" :placeholder="placeholder">{{label}}</slot>
{{label}}
</slot>
</span> </span>
<i v-if="showClear && modelValue != null" class="p-dropdown-clear-icon pi pi-times" @click="onClearClick($event)"></i> <i v-if="showClear && modelValue != null" class="p-dropdown-clear-icon pi pi-times" @click="onClearClick($event)"></i>
<div class="p-dropdown-trigger" role="button" aria-haspopup="listbox" :aria-expanded="overlayVisible"> <div class="p-dropdown-trigger" role="button" aria-haspopup="listbox" :aria-expanded="overlayVisible">
@ -17,6 +15,7 @@
</div> </div>
<transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave"> <transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave">
<div :ref="overlayRef" class="p-dropdown-panel p-component" v-if="overlayVisible"> <div :ref="overlayRef" class="p-dropdown-panel p-component" v-if="overlayVisible">
<slot name="header" :value="modelValue" :options="visibleOptions"></slot>
<div class="p-dropdown-header" v-if="filter"> <div class="p-dropdown-header" v-if="filter">
<div class="p-dropdown-filter-container"> <div class="p-dropdown-filter-container">
<input type="text" ref="filterInput" v-model="filterValue" autoComplete="off" class="p-dropdown-filter p-inputtext p-component" :placeholder="filterPlaceholder" @keydown="onFilterKeyDown" @input="onFilterChange"/> <input type="text" ref="filterInput" v-model="filterValue" autoComplete="off" class="p-dropdown-filter p-inputtext p-component" :placeholder="filterPlaceholder" @keydown="onFilterKeyDown" @input="onFilterChange"/>
@ -25,15 +24,32 @@
</div> </div>
<div class="p-dropdown-items-wrapper" :style="{'max-height': scrollHeight}"> <div class="p-dropdown-items-wrapper" :style="{'max-height': scrollHeight}">
<ul class="p-dropdown-items" role="listbox"> <ul class="p-dropdown-items" role="listbox">
<li v-for="(option, i) of visibleOptions" :class="['p-dropdown-item', {'p-highlight': isSelected(option), 'p-disabled': isOptionDisabled(option)}]" v-ripple <template v-if="!optionGroupLabel">
:aria-label="getOptionLabel(option)" :key="getOptionRenderKey(option)" @click="onOptionSelect($event, option)" role="option" :aria-selected="isSelected(option)"> <li v-for="(option, i) of visibleOptions" :class="['p-dropdown-item', {'p-highlight': isSelected(option), 'p-disabled': isOptionDisabled(option)}]" v-ripple
<slot name="option" :option="option" :index="i"> :key="getOptionRenderKey(option)" @click="onOptionSelect($event, option)" role="option" :aria-label="getOptionLabel(option)" :aria-selected="isSelected(option)">
{{getOptionLabel(option)}} <slot name="option" :option="option" :index="i">{{getOptionLabel(option)}}</slot>
</slot> </li>
</template>
<template v-else>
<template v-for="(optionGroup, i) of visibleOptions" :key="getOptionGroupRenderKey(optionGroup)">
<li class="p-dropdown-item-group" >
<slot name="optiongroup" :option="optionGroup" :index="i">{{getOptionGroupLabel(optionGroup)}}</slot>
</li>
<li v-for="(option, i) of getOptionGroupChildren(optionGroup)" :class="['p-dropdown-item', {'p-highlight': isSelected(option), 'p-disabled': isOptionDisabled(option)}]" v-ripple
:key="getOptionRenderKey(option)" @click="onOptionSelect($event, option)" role="option" :aria-label="getOptionLabel(option)" :aria-selected="isSelected(option)">
<slot name="option" :option="option" :index="i">{{getOptionLabel(option)}}</slot>
</li>
</template>
</template>
<li v-if="filterValue && (!visibleOptions || (visibleOptions && visibleOptions.length === 0))" class="p-dropdown-empty-message">
<slot name="emptyfilter">{{emptyFilterMessageText}}</slot>
</li>
<li v-else-if="(!options || (options && options.length === 0))" class="p-dropdown-empty-message">
<slot name="empty">{{emptyMessageText}}</slot>
</li> </li>
<li v-if="filterValue && (!visibleOptions || (visibleOptions && visibleOptions.length === 0))" class="p-dropdown-empty-message">{{emptyFilterMessage}}</li>
</ul> </ul>
</div> </div>
<slot name="footer" :value="modelValue" :options="visibleOptions"></slot>
</div> </div>
</transition> </transition>
</div> </div>
@ -43,6 +59,7 @@
import {ConnectedOverlayScrollHandler} from 'primevue/utils'; import {ConnectedOverlayScrollHandler} from 'primevue/utils';
import {ObjectUtils} from 'primevue/utils'; import {ObjectUtils} from 'primevue/utils';
import {DomHandler} from 'primevue/utils'; import {DomHandler} from 'primevue/utils';
import {FilterService} from 'primevue/api';
import Ripple from 'primevue/ripple'; import Ripple from 'primevue/ripple';
export default { export default {
@ -53,6 +70,8 @@ export default {
optionLabel: null, optionLabel: null,
optionValue: null, optionValue: null,
optionDisabled: null, optionDisabled: null,
optionGroupLabel: null,
optionGroupChildren: null,
scrollHeight: { scrollHeight: {
type: String, type: String,
default: '200px' default: '200px'
@ -60,6 +79,14 @@ export default {
filter: Boolean, filter: Boolean,
filterPlaceholder: String, filterPlaceholder: String,
filterLocale: String, filterLocale: String,
filterMatchMode: {
type: String,
default: 'contains'
},
filterFields: {
type: Array,
default: null
},
editable: Boolean, editable: Boolean,
placeholder: String, placeholder: String,
disabled: Boolean, disabled: Boolean,
@ -74,7 +101,11 @@ export default {
}, },
emptyFilterMessage: { emptyFilterMessage: {
type: String, type: String,
default: 'No results found' default: null
},
emptyMessage: {
type: String,
default: null
} }
}, },
data() { data() {
@ -116,37 +147,48 @@ export default {
isOptionDisabled(option) { isOptionDisabled(option) {
return this.optionDisabled ? ObjectUtils.resolveFieldData(option, this.optionDisabled) : false; return this.optionDisabled ? ObjectUtils.resolveFieldData(option, this.optionDisabled) : false;
}, },
getOptionGroupRenderKey(optionGroup) {
return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupLabel);
},
getOptionGroupLabel(optionGroup) {
return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupLabel);
},
getOptionGroupChildren(optionGroup) {
return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupChildren);
},
getSelectedOption() { getSelectedOption() {
let selectedOption; let index = this.getSelectedOptionIndex();
return index !== -1 ? (this.optionGroupLabel ? this.getOptionGroupChildren(this.options[index.group])[index.option]: this.options[index]) : null;
},
getSelectedOptionIndex() {
if (this.modelValue != null && this.options) { if (this.modelValue != null && this.options) {
for (let option of this.options) { if (this.optionGroupLabel) {
if ((ObjectUtils.equals(this.modelValue, this.getOptionValue(option), this.equalityKey))) { for (let i = 0; i < this.options.length; i++) {
selectedOption = option; let selectedOptionIndex = this.findOptionIndexInList(this.modelValue, this.getOptionGroupChildren(this.options[i]));
break; if (selectedOptionIndex !== -1) {
return {group: i, option: selectedOptionIndex};
}
} }
} }
else {
return this.findOptionIndexInList(this.modelValue, this.options);
}
}
return -1;
},
findOptionIndexInList(value, list) {
for (let i = 0; i < list.length; i++) {
if ((ObjectUtils.equals(value, this.getOptionValue(list[i]), this.equalityKey))) {
return i;
}
} }
return selectedOption; return -1;
}, },
isSelected(option) { isSelected(option) {
return ObjectUtils.equals(this.modelValue, this.getOptionValue(option), this.equalityKey); return ObjectUtils.equals(this.modelValue, this.getOptionValue(option), this.equalityKey);
}, },
getSelectedOptionIndex() {
let selectedOptionIndex = -1;
if (this.modelValue != null && this.visibleOptions) {
for (let i = 0; i < this.visibleOptions.length; i++) {
if ((ObjectUtils.equals(this.modelValue, this.getOptionValue(this.visibleOptions[i]), this.equalityKey))) {
selectedOptionIndex = i;
break;
}
}
}
return selectedOptionIndex;
},
show() { show() {
this.$emit('before-show'); this.$emit('before-show');
this.overlayVisible = true; this.overlayVisible = true;
@ -230,7 +272,6 @@ export default {
} }
else { else {
let nextOption = this.findNextOption(this.getSelectedOptionIndex()); let nextOption = this.findNextOption(this.getSelectedOptionIndex());
if (nextOption) { if (nextOption) {
this.updateModel(event, this.getOptionValue(nextOption)); this.updateModel(event, this.getOptionValue(nextOption));
} }
@ -242,7 +283,6 @@ export default {
onUpKey(event) { onUpKey(event) {
if (this.visibleOptions) { if (this.visibleOptions) {
let prevOption = this.findPrevOption(this.getSelectedOptionIndex()); let prevOption = this.findPrevOption(this.getSelectedOptionIndex());
if (prevOption) { if (prevOption) {
this.updateModel(event, this.getOptionValue(prevOption)); this.updateModel(event, this.getOptionValue(prevOption));
} }
@ -251,25 +291,62 @@ export default {
event.preventDefault(); event.preventDefault();
}, },
findNextOption(index) { findNextOption(index) {
let i = index + 1; if (this.optionGroupLabel) {
if (i === this.visibleOptions.length) { let groupIndex = index === -1 ? 0 : index.group;
let optionIndex = index === -1 ? -1 : index.option;
let option = this.findNextOptionInList(this.getOptionGroupChildren(this.visibleOptions[groupIndex]), optionIndex);
if (option)
return option;
else if ((groupIndex + 1) !== this.visibleOptions.length)
return this.findNextOption({group: (groupIndex + 1), option: -1});
else
return null;
}
else {
return this.findNextOptionInList(this.visibleOptions, index);
}
},
findNextOptionInList(list, index) {
let i = index + 1;
if (i === list.length) {
return null;
}
let option = list[i];
if (this.isOptionDisabled(option))
return this.findNextOptionInList(i);
else
return option;
},
findPrevOption(index) {
if (index === -1) {
return null; return null;
} }
let option = this.visibleOptions[i]; if (this.optionGroupLabel) {
if (this.isOptionDisabled(option)) let groupIndex = index.group;
return this.findNextOption(i); let optionIndex = index.option;
else let option = this.findPrevOptionInList(this.getOptionGroupChildren(this.visibleOptions[groupIndex]), optionIndex);
return option;
if (option)
return option;
else if (groupIndex > 0)
return this.findPrevOption({group: (groupIndex - 1), option: this.getOptionGroupChildren(this.visibleOptions[groupIndex - 1]).length});
else
return null;
}
else {
return this.findPrevOptionInList(this.visibleOptions, index);
}
}, },
findPrevOption(index) { findPrevOptionInList(list, index) {
let i = index - 1; let i = index - 1;
if (i < 0) { if (i < 0) {
return null; return null;
} }
let option = this.visibleOptions[i]; let option = list[i];
if (this.isOptionDisabled(option)) if (this.isOptionDisabled(option))
return this.findPrevOption(i); return this.findPrevOption(i);
else else
@ -406,13 +483,14 @@ export default {
else else
this.searchValue = this.searchValue ? this.searchValue + char : char; this.searchValue = this.searchValue ? this.searchValue + char : char;
let searchIndex = this.getSelectedOptionIndex(); if (this.searchValue) {
let newOption = this.searchOption(++searchIndex); let searchIndex = this.getSelectedOptionIndex();
let newOption = this.optionGroupLabel ? this.searchOptionInGroup(searchIndex) : this.searchOption(++searchIndex);
if (newOption) { if (newOption) {
this.updateModel(event, this.getOptionValue(newOption)); this.updateModel(event, this.getOptionValue(newOption));
}
} }
this.searchTimeout = setTimeout(() => { this.searchTimeout = setTimeout(() => {
this.searchValue = null; this.searchValue = null;
}, 250); }, 250);
@ -433,14 +511,40 @@ export default {
searchOptionInRange(start, end) { searchOptionInRange(start, end) {
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
let opt = this.visibleOptions[i]; let opt = this.visibleOptions[i];
let label = this.getOptionLabel(opt).toLocaleLowerCase(this.filterLocale); if (this.matchesSearchValue(opt)) {
if (label.startsWith(this.searchValue.toLocaleLowerCase(this.filterLocale))) {
return opt; return opt;
} }
} }
return null; return null;
}, },
searchOptionInGroup(index) {
let searchIndex = index === -1 ? {group: 0, option: -1} : index;
for (let i = searchIndex.group; i < this.visibleOptions.length; i++) {
let groupOptions = this.getOptionGroupChildren(this.visibleOptions[i]);
for (let j = (searchIndex.group === i ? searchIndex.option + 1 : 0); j < groupOptions.length; j++) {
if (this.matchesSearchValue(groupOptions[j])) {
return groupOptions[j];
}
}
}
for (let i = 0; i <= searchIndex.group; i++) {
let groupOptions = this.getOptionGroupChildren(this.visibleOptions[i]);
for (let j = 0; j < (searchIndex.group === i ? searchIndex.option: groupOptions.length); j++) {
if (this.matchesSearchValue(groupOptions[j])) {
return groupOptions[j];
}
}
}
return null;
},
matchesSearchValue(option) {
let label = this.getOptionLabel(option).toLocaleLowerCase(this.filterLocale);
return label.startsWith(this.searchValue.toLocaleLowerCase(this.filterLocale));
},
appendContainer() { appendContainer() {
if (this.appendTo) { if (this.appendTo) {
if (this.appendTo === 'body') if (this.appendTo === 'body')
@ -469,10 +573,24 @@ export default {
}, },
computed: { computed: {
visibleOptions() { visibleOptions() {
if (this.filterValue && this.filterValue.trim().length > 0) if (this.filterValue) {
return this.options.filter(option => this.getOptionLabel(option).toLocaleLowerCase(this.filterLocale).indexOf(this.filterValue.toLocaleLowerCase(this.filterLocale)) > -1); if (this.optionGroupLabel) {
else let filteredGroups = [];
for (let optgroup of this.options) {
let filteredSubOptions = FilterService.filter(this.getOptionGroupChildren(optgroup), this.searchFields, this.filterValue, this.filterMatchMode, this.filterLocale);
if (filteredSubOptions && filteredSubOptions.length) {
filteredGroups.push({...optgroup, ...{items: filteredSubOptions}});
}
}
return filteredGroups
}
else {
return FilterService.filter(this.options, this.searchFields, this.filterValue, 'contains', this.filterLocale);
}
}
else {
return this.options; return this.options;
}
}, },
containerClass() { containerClass() {
return [ return [
@ -511,6 +629,15 @@ export default {
}, },
equalityKey() { equalityKey() {
return this.optionValue ? null : this.dataKey; return this.optionValue ? null : this.dataKey;
},
searchFields() {
return this.filterFields || [this.optionLabel];
},
emptyFilterMessageText() {
return this.emptyFilterMessage || this.$primevue.config.locale.emptyFilterMessage;
},
emptyMessageText() {
return this.emptyMessage || this.$primevue.config.locale.emptyMessage;
} }
}, },
directives: { directives: {