Fixed #976 - AutoComplete Enhancements
parent
72dbd25535
commit
d2cbbba647
|
@ -4,6 +4,8 @@ interface AutoCompleteProps {
|
|||
modelValue?: any;
|
||||
suggestions?: any[];
|
||||
field?: string|Function;
|
||||
optionGroupLabel?: string;
|
||||
optionGroupChildren?: string;
|
||||
scrollHeight?: string;
|
||||
dropdown?: boolean;
|
||||
dropdownMode?: string;
|
||||
|
|
|
@ -16,13 +16,25 @@
|
|||
<Button ref="dropdownButton" type="button" icon="pi pi-chevron-down" class="p-autocomplete-dropdown" :disabled="$attrs.disabled" @click="onDropdownClick" v-if="dropdown"/>
|
||||
<transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave">
|
||||
<div :ref="overlayRef" class="p-autocomplete-panel p-component" :style="{'max-height': scrollHeight}" v-if="overlayVisible">
|
||||
<slot name="header" :value="modelValue" :suggestions="suggestions"></slot>
|
||||
<ul :id="listId" class="p-autocomplete-items" role="listbox">
|
||||
<li v-for="(item, i) of suggestions" class="p-autocomplete-item" :key="i" @click="selectItem($event, item)" role="option" v-ripple>
|
||||
<slot name="item" :item="item" :index="i">
|
||||
{{getItemContent(item)}}
|
||||
</slot>
|
||||
</li>
|
||||
<template v-if="!optionGroupLabel">
|
||||
<li v-for="(item, i) of suggestions" class="p-autocomplete-item" :key="i" @click="selectItem($event, item)" role="option" v-ripple>
|
||||
<slot name="item" :item="item" :index="i">{{getItemContent(item)}}</slot>
|
||||
</li>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-for="(optionGroup, i) of suggestions" :key="getOptionGroupRenderKey(optionGroup)">
|
||||
<li class="p-autocomplete-item-group" >
|
||||
<slot name="optiongroup" :item="optionGroup" :index="i">{{getOptionGroupLabel(optionGroup)}}</slot>
|
||||
</li>
|
||||
<li v-for="(item, i) of getOptionGroupChildren(optionGroup)" class="p-autocomplete-item" :key="i" @click="selectItem($event, item)" role="option" v-ripple>
|
||||
<slot name="item" :item="item" :index="i">{{getItemContent(item)}}</slot>
|
||||
</li>
|
||||
</template>
|
||||
</template>
|
||||
</ul>
|
||||
<slot name="footer" :value="modelValue" :suggestions="suggestions"></slot>
|
||||
</div>
|
||||
</transition>
|
||||
</span>
|
||||
|
@ -49,6 +61,8 @@ export default {
|
|||
type: [String,Function],
|
||||
default: null
|
||||
},
|
||||
optionGroupLabel: null,
|
||||
optionGroupChildren: null,
|
||||
scrollHeight: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
|
@ -102,7 +116,6 @@ export default {
|
|||
watch: {
|
||||
suggestions() {
|
||||
if (this.searching) {
|
||||
|
||||
if (this.suggestions && this.suggestions.length)
|
||||
this.showOverlay();
|
||||
else
|
||||
|
@ -129,6 +142,15 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
getOptionGroupRenderKey(optionGroup) {
|
||||
return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupLabel);
|
||||
},
|
||||
getOptionGroupLabel(optionGroup) {
|
||||
return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupLabel);
|
||||
},
|
||||
getOptionGroupChildren(optionGroup) {
|
||||
return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupChildren);
|
||||
},
|
||||
onOverlayEnter() {
|
||||
this.overlay.style.zIndex = String(DomHandler.generateZIndex());
|
||||
this.appendContainer();
|
||||
|
@ -328,7 +350,7 @@ export default {
|
|||
//down
|
||||
case 40:
|
||||
if (highlightItem) {
|
||||
let nextElement = highlightItem.nextElementSibling;
|
||||
let nextElement = this.findNextItem(highlightItem);
|
||||
if (nextElement) {
|
||||
DomHandler.addClass(nextElement, 'p-highlight');
|
||||
DomHandler.removeClass(highlightItem, 'p-highlight');
|
||||
|
@ -336,7 +358,14 @@ export default {
|
|||
}
|
||||
}
|
||||
else {
|
||||
DomHandler.addClass(this.overlay.firstChild.firstElementChild, 'p-highlight');
|
||||
highlightItem = this.overlay.firstElementChild.firstElementChild;
|
||||
if (DomHandler.hasClass(highlightItem, 'p-autocomplete-item-group')) {
|
||||
highlightItem = this.findNextItem(highlightItem);
|
||||
}
|
||||
|
||||
if (highlightItem) {
|
||||
DomHandler.addClass(highlightItem, 'p-highlight');
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
@ -345,7 +374,7 @@ export default {
|
|||
//up
|
||||
case 38:
|
||||
if (highlightItem) {
|
||||
let previousElement = highlightItem.previousElementSibling;
|
||||
let previousElement = this.findPrevItem(highlightItem);
|
||||
if (previousElement) {
|
||||
DomHandler.addClass(previousElement, 'p-highlight');
|
||||
DomHandler.removeClass(highlightItem, 'p-highlight');
|
||||
|
@ -407,6 +436,22 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
findNextItem(item) {
|
||||
let nextItem = item.nextElementSibling;
|
||||
|
||||
if (nextItem)
|
||||
return DomHandler.hasClass(nextItem, 'p-autocomplete-item-group') ? this.findNextItem(nextItem) : nextItem;
|
||||
else
|
||||
return null;
|
||||
},
|
||||
findPrevItem(item) {
|
||||
let prevItem = item.previousElementSibling;
|
||||
|
||||
if (prevItem)
|
||||
return DomHandler.hasClass(prevItem, 'p-autocomplete-item-group') ? this.findPrevItem(prevItem) : prevItem;
|
||||
else
|
||||
return null;
|
||||
},
|
||||
onChange(event) {
|
||||
if (this.forceSelection) {
|
||||
let valid = false;
|
||||
|
|
|
@ -13,6 +13,16 @@
|
|||
<h5>Basic</h5>
|
||||
<AutoComplete v-model="selectedCountry1" :suggestions="filteredCountries" @complete="searchCountry($event)" field="name" />
|
||||
|
||||
<h5>Grouped</h5>
|
||||
<AutoComplete v-model="selectedCity" :suggestions="filteredCities" @complete="searchCity($event)" field="label" optionGroupLabel="label" optionGroupChildren="items">
|
||||
<template #optiongroup="slotProps">
|
||||
<div class="p-d-flex p-ai-center country-item">
|
||||
<img src="../../assets/images/flag_placeholder.png" :class="'flag flag-' + slotProps.item.code.toLowerCase()" width="18" />
|
||||
<div>{{slotProps.item.label}}</div>
|
||||
</div>
|
||||
</template>
|
||||
</AutoComplete>
|
||||
|
||||
<h5>Dropdown, Templating and Force Selection</h5>
|
||||
<AutoComplete v-model="selectedCountry2" :suggestions="filteredCountries" @complete="searchCountry($event)" :dropdown="true" field="name" forceSelection>
|
||||
<template #item="slotProps">
|
||||
|
@ -37,6 +47,7 @@
|
|||
<script>
|
||||
import CountryService from '../../service/CountryService';
|
||||
import AutoCompleteDoc from './AutoCompleteDoc';
|
||||
import {FilterService,FilterMatchMode} from 'primevue/api';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -44,8 +55,37 @@ export default {
|
|||
countries: null,
|
||||
selectedCountry1: null,
|
||||
selectedCountry2: null,
|
||||
selectedCity: null,
|
||||
filteredCities: null,
|
||||
filteredCountries: null,
|
||||
selectedCountries: []
|
||||
selectedCountries: [],
|
||||
groupedCities: [{
|
||||
label: 'Germany', code: 'DE',
|
||||
items: [
|
||||
{label: 'Berlin', value: 'Berlin'},
|
||||
{label: 'Frankfurt', value: 'Frankfurt'},
|
||||
{label: 'Hamburg', value: 'Hamburg'},
|
||||
{label: 'Munich', value: 'Munich'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'USA', code: 'US',
|
||||
items: [
|
||||
{label: 'Chicago', value: 'Chicago'},
|
||||
{label: 'Los Angeles', value: 'Los Angeles'},
|
||||
{label: 'New York', value: 'New York'},
|
||||
{label: 'San Francisco', value: 'San Francisco'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Japan', code: 'JP',
|
||||
items: [
|
||||
{label: 'Kyoto', value: 'Kyoto'},
|
||||
{label: 'Osaka', value: 'Osaka'},
|
||||
{label: 'Tokyo', value: 'Tokyo'},
|
||||
{label: 'Yokohama', value: 'Yokohama'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
},
|
||||
countryService: null,
|
||||
|
@ -67,6 +107,19 @@ export default {
|
|||
});
|
||||
}
|
||||
}, 250);
|
||||
},
|
||||
searchCity(event) {
|
||||
let query = event.query;
|
||||
let filteredCities = [];
|
||||
|
||||
for (let country of this.groupedCities) {
|
||||
let filteredItems = FilterService.filter(country.items, ['label'], query, FilterMatchMode.CONTAINS);
|
||||
if (filteredItems && filteredItems.length) {
|
||||
filteredCities.push({...country, ...{items: filteredItems}});
|
||||
}
|
||||
}
|
||||
|
||||
this.filteredCities = filteredCities;
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
|
@ -60,6 +60,51 @@ export default {
|
|||
<pre v-code><code>
|
||||
<AutoComplete field="label" v-model="selectedCountry" :suggestions="filteredCountriesBasic" @complete="searchCountryBasic($event)" />
|
||||
|
||||
</code></pre>
|
||||
|
||||
<h5>Grouping</h5>
|
||||
<p>Options groups are specified with the <i>optionGroupLabel</i> and <i>optionGroupChildren</i> properties.</p>
|
||||
<pre v-code.script><code>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectedGroupedCity: null,
|
||||
groupedCities: [{
|
||||
label: 'Germany', code: 'DE',
|
||||
items: [
|
||||
{label: 'Berlin', value: 'Berlin'},
|
||||
{label: 'Frankfurt', value: 'Frankfurt'},
|
||||
{label: 'Hamburg', value: 'Hamburg'},
|
||||
{label: 'Munich', value: 'Munich'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'USA', code: 'US',
|
||||
items: [
|
||||
{label: 'Chicago', value: 'Chicago'},
|
||||
{label: 'Los Angeles', value: 'Los Angeles'},
|
||||
{label: 'New York', value: 'New York'},
|
||||
{label: 'San Francisco', value: 'San Francisco'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Japan', code: 'JP',
|
||||
items: [
|
||||
{label: 'Kyoto', value: 'Kyoto'},
|
||||
{label: 'Osaka', value: 'Osaka'},
|
||||
{label: 'Tokyo', value: 'Tokyo'},
|
||||
{label: 'Yokohama', value: 'Yokohama'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<pre v-code><code><template v-pre>
|
||||
<AutoComplete v-model="selectedCity" :suggestions="filteredCities" @complete="searchCity($event)"
|
||||
field="label" optionGroupLabel="label" optionGroupChildren="items"></AutoComplete>
|
||||
</template>
|
||||
</code></pre>
|
||||
|
||||
<h5>Force Selection</h5>
|
||||
|
@ -71,10 +116,11 @@ export default {
|
|||
</code></pre>
|
||||
|
||||
<h5>Templating</h5>
|
||||
<p>Item template allows displaying custom content inside the suggestions panel. The slotProps variable passed to the template provides an item property to represent an item in the suggestions collection.</p>
|
||||
<p>Item template allows displaying custom content inside the suggestions panel. The slotProps variable passed to the template provides an item property to represent an item in the suggestions collection.
|
||||
In addition <i>optiongroup</i>, <i>header</i> and <i>footer</i> slots are provided for further customization</p>
|
||||
<pre v-code><code><template v-pre>
|
||||
<AutoComplete v-model="brand" :suggestions="filteredBrands" @complete="searchBrand($event)" placeholder="Hint: type 'v' or 'f'" :dropdown="true">
|
||||
<template #item="slotProps">
|
||||
<template #item="slotProps">
|
||||
<img :alt="slotProps.item" :src="'demo/images/car/' + slotProps.item + '.png'" />
|
||||
<div>{{slotProps.item}}</div>
|
||||
</template>
|
||||
|
@ -113,6 +159,18 @@ export default {
|
|||
<td>null</td>
|
||||
<td>Property name or getter function of a suggested object to resolve and display.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>optionGroupLabel</td>
|
||||
<td>string</td>
|
||||
<td>null</td>
|
||||
<td>Property name or getter function to use as the label of an option group.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>optionGroupChildren</td>
|
||||
<td>string</td>
|
||||
<td>null</td>
|
||||
<td>Property name or getter function that refers to the children options of option group.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>scrollHeight</td>
|
||||
<td>string</td>
|
||||
|
@ -227,6 +285,40 @@ export default {
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<h5>Slots</h5>
|
||||
<div class="doc-tablewrapper">
|
||||
<table class="doc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Parameters</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>item</td>
|
||||
<td>item: Option instance <br />
|
||||
index: Index of the option</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>optiongroup</td>
|
||||
<td>item: OptionGroup instance <br />
|
||||
index: Index of the option group</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>header</td>
|
||||
<td>value: Value of the component <br />
|
||||
suggestions: Displayed options</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>footer</td>
|
||||
<td>value: Value of the component <br />
|
||||
suggestions: Displayed options</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h5>Styling</h5>
|
||||
<p>Following is the list of structural style classes</p>
|
||||
<div class="doc-tablewrapper">
|
||||
|
@ -285,6 +377,16 @@ export default {
|
|||
<h5>Basic</h5>
|
||||
<AutoComplete v-model="selectedCountry1" :suggestions="filteredCountries" @complete="searchCountry($event)" field="name" />
|
||||
|
||||
<h5>Grouped</h5>
|
||||
<AutoComplete v-model="selectedCity" :suggestions="filteredCities" @complete="searchCity($event)" field="label" optionGroupLabel="label" optionGroupChildren="items">
|
||||
<template #optiongroup="slotProps">
|
||||
<div class="p-d-flex p-ai-center country-item">
|
||||
<img src="../../assets/images/flag_placeholder.png" :class="'flag flag-' + slotProps.item.code.toLowerCase()" width="18" />
|
||||
<div>{{slotProps.item.label}}</div>
|
||||
</div>
|
||||
</template>
|
||||
</AutoComplete>
|
||||
|
||||
<h5>Dropdown, Templating and Force Selection</h5>
|
||||
<AutoComplete v-model="selectedCountry2" :suggestions="filteredCountries" @complete="searchCountry($event)" :dropdown="true" field="name" forceSelection>
|
||||
<template #item="slotProps">
|
||||
|
@ -304,6 +406,7 @@ export default {
|
|||
|
||||
<pre v-code.script><code>
|
||||
import CountryService from '../../service/CountryService';
|
||||
import {FilterService,FilterMatchMode} from 'primevue/api';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -311,8 +414,37 @@ export default {
|
|||
countries: null,
|
||||
selectedCountry1: null,
|
||||
selectedCountry2: null,
|
||||
selectedCity: null,
|
||||
filteredCities: null,
|
||||
filteredCountries: null,
|
||||
selectedCountries: []
|
||||
selectedCountries: [],
|
||||
groupedCities: [{
|
||||
label: 'Germany', code: 'DE',
|
||||
items: [
|
||||
{label: 'Berlin', value: 'Berlin'},
|
||||
{label: 'Frankfurt', value: 'Frankfurt'},
|
||||
{label: 'Hamburg', value: 'Hamburg'},
|
||||
{label: 'Munich', value: 'Munich'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'USA', code: 'US',
|
||||
items: [
|
||||
{label: 'Chicago', value: 'Chicago'},
|
||||
{label: 'Los Angeles', value: 'Los Angeles'},
|
||||
{label: 'New York', value: 'New York'},
|
||||
{label: 'San Francisco', value: 'San Francisco'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Japan', code: 'JP',
|
||||
items: [
|
||||
{label: 'Kyoto', value: 'Kyoto'},
|
||||
{label: 'Osaka', value: 'Osaka'},
|
||||
{label: 'Tokyo', value: 'Tokyo'},
|
||||
{label: 'Yokohama', value: 'Yokohama'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
},
|
||||
countryService: null,
|
||||
|
@ -334,6 +466,19 @@ export default {
|
|||
});
|
||||
}
|
||||
}, 250);
|
||||
},
|
||||
searchCity(event) {
|
||||
let query = event.query;
|
||||
let filteredCities = [];
|
||||
|
||||
for (let country of this.groupedCities) {
|
||||
let filteredItems = FilterService.filter(country.items, ['label'], query, FilterMatchMode.CONTAINS);
|
||||
if (filteredItems && filteredItems.length) {
|
||||
filteredCities.push({...country, ...{items: filteredItems}});
|
||||
}
|
||||
}
|
||||
|
||||
this.filteredCities = filteredCities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -358,11 +503,21 @@ export default {
|
|||
<h5>Basic</h5>
|
||||
<AutoComplete v-model="selectedCountry1" :suggestions="filteredCountries" @complete="searchCountry($event)" field="name" />
|
||||
|
||||
<h5>Grouped</h5>
|
||||
<AutoComplete v-model="selectedCity" :suggestions="filteredCities" @complete="searchCity($event)" field="label" optionGroupLabel="label" optionGroupChildren="items">
|
||||
<template #optiongroup="slotProps">
|
||||
<div class="p-d-flex p-ai-center country-item">
|
||||
<img src="../../assets/images/flag_placeholder.png" :class="'flag flag-' + slotProps.item.code.toLowerCase()" width="18" />
|
||||
<div>{{slotProps.item.label}}</div>
|
||||
</div>
|
||||
</template>
|
||||
</AutoComplete>
|
||||
|
||||
<h5>Dropdown, Templating and Force Selection</h5>
|
||||
<AutoComplete v-model="selectedCountry2" :suggestions="filteredCountries" @complete="searchCountry($event)" :dropdown="true" field="name" forceSelection>
|
||||
<template #item="slotProps">
|
||||
<div class="country-item">
|
||||
<img src="https://www.primefaces.org/wp-content/uploads/2020/05/placeholder.png" />
|
||||
<img src="../../assets/images/flag_placeholder.png" :class="'flag flag-' + slotProps.item.code.toLowerCase()" />
|
||||
<div>{{slotProps.item.name}}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -378,15 +533,46 @@ export default {
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import CountryService from '../service/CountryService';
|
||||
import CountryService from '../../service/CountryService';
|
||||
import {FilterService,FilterMatchMode} from 'primevue/api';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
countries: null,
|
||||
selectedCountry1: null,
|
||||
selectedCountry2: null,
|
||||
selectedCity: null,
|
||||
filteredCities: null,
|
||||
filteredCountries: null,
|
||||
selectedCountries: []
|
||||
selectedCountries: [],
|
||||
groupedCities: [{
|
||||
label: 'Germany', code: 'DE',
|
||||
items: [
|
||||
{label: 'Berlin', value: 'Berlin'},
|
||||
{label: 'Frankfurt', value: 'Frankfurt'},
|
||||
{label: 'Hamburg', value: 'Hamburg'},
|
||||
{label: 'Munich', value: 'Munich'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'USA', code: 'US',
|
||||
items: [
|
||||
{label: 'Chicago', value: 'Chicago'},
|
||||
{label: 'Los Angeles', value: 'Los Angeles'},
|
||||
{label: 'New York', value: 'New York'},
|
||||
{label: 'San Francisco', value: 'San Francisco'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Japan', code: 'JP',
|
||||
items: [
|
||||
{label: 'Kyoto', value: 'Kyoto'},
|
||||
{label: 'Osaka', value: 'Osaka'},
|
||||
{label: 'Tokyo', value: 'Tokyo'},
|
||||
{label: 'Yokohama', value: 'Yokohama'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
},
|
||||
countryService: null,
|
||||
|
@ -408,6 +594,19 @@ export default {
|
|||
});
|
||||
}
|
||||
}, 250);
|
||||
},
|
||||
searchCity(event) {
|
||||
let query = event.query;
|
||||
let filteredCities = [];
|
||||
|
||||
for (let country of this.groupedCities) {
|
||||
let filteredItems = FilterService.filter(country.items, ['label'], query, FilterMatchMode.CONTAINS);
|
||||
if (filteredItems && filteredItems.length) {
|
||||
filteredCities.push({...country, ...{items: filteredItems}});
|
||||
}
|
||||
}
|
||||
|
||||
this.filteredCities = filteredCities;
|
||||
}
|
||||
}
|
||||
}`,
|
||||
|
|
|
@ -100,14 +100,12 @@ export default {
|
|||
In addition <i>optiongroup</i>, <i>header</i>, <i>footer</i>, <i>emptyfilter</i> and <i>empty</i> slots are provided for further customization.</p>
|
||||
<pre v-code><code><template v-pre>
|
||||
<Listbox v-model="selectedCars" :options="cars" :multiple="true" :filter="true" optionLabel="brand" listStyle="max-height:250px" style="width:15em">
|
||||
<template #header></template>
|
||||
<template #option="slotProps">
|
||||
<div>
|
||||
<img :alt="slotProps.option.brand" :src="'demo/images/car/' + slotProps.option.brand + '.png'" />
|
||||
<span>{{slotProps.option.brand}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer></footer>
|
||||
</Listbox>
|
||||
</template>
|
||||
</code></pre>
|
||||
|
|
Loading…
Reference in New Issue