pull/978/head
parent
7b6e8e2e68
commit
75b2ff8561
|
@ -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: {
|
||||||
|
|
Loading…
Reference in New Issue