pull/978/head
Cagatay Civici 2021-02-15 17:05:02 +03:00
parent f89d755530
commit 72dbd25535
8 changed files with 543 additions and 107 deletions

View File

@ -706,6 +706,10 @@ input.p-dropdown-label {
overflow: hidden; overflow: hidden;
} }
.p-dropdown-item-group {
cursor: auto;
}
.p-dropdown-items { .p-dropdown-items {
margin: 0; margin: 0;
padding: 0; padding: 0;

View File

@ -326,6 +326,10 @@ export default {
overflow: hidden; overflow: hidden;
} }
.p-listbox-item-group {
cursor: auto;
}
.p-listbox-filter-container { .p-listbox-filter-container {
position: relative; position: relative;
} }

View File

@ -6,18 +6,23 @@ interface MultiSelectProps {
optionLabel?: string; optionLabel?: string;
optionValue?: any; optionValue?: any;
optionDisabled?: boolean; optionDisabled?: boolean;
optionGroupLabel?: string;
optionGroupChildren?: string;
scrollHeight?: string; scrollHeight?: string;
placeholder?: string; placeholder?: string;
disabled?: boolean; disabled?: boolean;
filter?: boolean;
tabindex?: string; tabindex?: string;
inputId?: string; inputId?: string;
dataKey?: string; dataKey?: string;
filter?: boolean;
filterPlaceholder?: string; filterPlaceholder?: string;
filterLocale?: string; filterLocale?: string;
filterMatchMode?: string;
filterFields?: string[];
ariaLabelledBy?: string; ariaLabelledBy?: string;
appendTo?: string; appendTo?: string;
emptyFilterMessage?: string; emptyFilterMessage?: string;
emptyMessage?: string;
display?: string; display?: string;
} }

View File

@ -25,6 +25,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-multiselect-panel p-component" v-if="overlayVisible"> <div :ref="overlayRef" class="p-multiselect-panel p-component" v-if="overlayVisible">
<slot name="header" :value="modelValue" :options="visibleOptions"></slot>
<div class="p-multiselect-header"> <div class="p-multiselect-header">
<div class="p-checkbox p-component" @click="onToggleAll" role="checkbox" :aria-checked="allSelected"> <div class="p-checkbox p-component" @click="onToggleAll" role="checkbox" :aria-checked="allSelected">
<div class="p-hidden-accessible"> <div class="p-hidden-accessible">
@ -44,8 +45,9 @@
</div> </div>
<div class="p-multiselect-items-wrapper" :style="{'max-height': scrollHeight}"> <div class="p-multiselect-items-wrapper" :style="{'max-height': scrollHeight}">
<ul class="p-multiselect-items p-component" role="listbox" aria-multiselectable="true"> <ul class="p-multiselect-items p-component" role="listbox" aria-multiselectable="true">
<template v-if="!optionGroupLabel">
<li v-for="(option, i) of visibleOptions" :class="['p-multiselect-item', {'p-highlight': isSelected(option), 'p-disabled': isOptionDisabled(option)}]" role="option" :aria-selected="isSelected(option)" <li v-for="(option, i) of visibleOptions" :class="['p-multiselect-item', {'p-highlight': isSelected(option), 'p-disabled': isOptionDisabled(option)}]" role="option" :aria-selected="isSelected(option)"
:aria-label="getOptionLabel(option)" :key="getOptionRenderKey(option)" @click="onOptionSelect($event, option)" @keydown="onOptionKeyDown($event, option)" :tabindex="tabindex||'0'" v-ripple> :key="getOptionRenderKey(option)" @click="onOptionSelect($event, option)" @keydown="onOptionKeyDown($event, option)" :tabindex="tabindex||'0'" :aria-label="getOptionLabel(option)" v-ripple>
<div class="p-checkbox p-component"> <div class="p-checkbox p-component">
<div :class="['p-checkbox-box', {'p-highlight': isSelected(option)}]"> <div :class="['p-checkbox-box', {'p-highlight': isSelected(option)}]">
<span :class="['p-checkbox-icon', {'pi pi-check': isSelected(option)}]"></span> <span :class="['p-checkbox-icon', {'pi pi-check': isSelected(option)}]"></span>
@ -55,9 +57,34 @@
<span>{{getOptionLabel(option)}}</span> <span>{{getOptionLabel(option)}}</span>
</slot> </slot>
</li> </li>
<li v-if="filterValue && (!visibleOptions || (visibleOptions && visibleOptions.length === 0))" class="p-multiselect-empty-message">{{emptyFilterMessage}}</li> </template>
<template v-else>
<template v-for="(optionGroup, i) of visibleOptions" :key="getOptionGroupRenderKey(optionGroup)">
<li class="p-multiselect-item-group">
<slot name="optiongroup" :option="optionGroup" :index="i">{{getOptionGroupLabel(optionGroup)}}</slot>
</li>
<li v-for="(option, i) of getOptionGroupChildren(optionGroup)" :class="['p-multiselect-item', {'p-highlight': isSelected(option), 'p-disabled': isOptionDisabled(option)}]" role="option" :aria-selected="isSelected(option)"
:key="getOptionRenderKey(option)" @click="onOptionSelect($event, option)" @keydown="onOptionKeyDown($event, option)" :tabindex="tabindex||'0'" :aria-label="getOptionLabel(option)" v-ripple>
<div class="p-checkbox p-component">
<div :class="['p-checkbox-box', {'p-highlight': isSelected(option)}]">
<span :class="['p-checkbox-icon', {'pi pi-check': isSelected(option)}]"></span>
</div>
</div>
<slot name="option" :option="option" :index="i">
<span>{{getOptionLabel(option)}}</span>
</slot>
</li>
</template>
</template>
<li v-if="filterValue && (!visibleOptions || (visibleOptions && visibleOptions.length === 0))" class="p-multiselect-empty-message">
<slot name="emptyfilter">{{emptyFilterMessageText}}</slot>
</li>
<li v-else-if="(!options || (options && options.length === 0))" class="p-multiselect-empty-message">
<slot name="empty">{{emptyMessageText}}</slot>
</li>
</ul> </ul>
</div> </div>
<slot name="footer" :value="modelValue" :options="visibleOptions"></slot>
</div> </div>
</transition> </transition>
</div> </div>
@ -67,6 +94,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 {
@ -77,18 +105,28 @@ 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'
}, },
placeholder: String, placeholder: String,
disabled: Boolean, disabled: Boolean,
filter: Boolean,
tabindex: String, tabindex: String,
inputId: String, inputId: String,
dataKey: null, dataKey: null,
filter: Boolean,
filterPlaceholder: String, filterPlaceholder: String,
filterLocale: String, filterLocale: String,
filterMatchMode: {
type: String,
default: 'contains'
},
filterFields: {
type: Array,
default: null
},
ariaLabelledBy: null, ariaLabelledBy: null,
appendTo: { appendTo: {
type: String, type: String,
@ -96,7 +134,11 @@ export default {
}, },
emptyFilterMessage: { emptyFilterMessage: {
type: String, type: String,
default: 'No results found' default: null
},
emptyMessage: {
type: String,
default: null
}, },
display: { display: {
type: String, type: String,
@ -136,6 +178,15 @@ export default {
getOptionRenderKey(option) { getOptionRenderKey(option) {
return this.dataKey ? ObjectUtils.resolveFieldData(option, this.dataKey) : this.getOptionLabel(option); return this.dataKey ? ObjectUtils.resolveFieldData(option, this.dataKey) : this.getOptionLabel(option);
}, },
getOptionGroupRenderKey(optionGroup) {
return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupLabel);
},
getOptionGroupLabel(optionGroup) {
return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupLabel);
},
getOptionGroupChildren(optionGroup) {
return ObjectUtils.resolveFieldData(optionGroup, this.optionGroupChildren);
},
isOptionDisabled(option) { isOptionDisabled(option) {
return this.optionDisabled ? ObjectUtils.resolveFieldData(option, this.optionDisabled) : false; return this.optionDisabled ? ObjectUtils.resolveFieldData(option, this.optionDisabled) : false;
}, },
@ -277,7 +328,7 @@ export default {
let nextItem = item.nextElementSibling; let nextItem = item.nextElementSibling;
if (nextItem) if (nextItem)
return DomHandler.hasClass(nextItem, 'p-disabled') ? this.findNextItem(nextItem) : nextItem; return DomHandler.hasClass(nextItem, 'p-disabled') || DomHandler.hasClass(nextItem, 'p-multiselect-item-group') ? this.findNextItem(nextItem) : nextItem;
else else
return null; return null;
}, },
@ -285,7 +336,7 @@ export default {
let prevItem = item.previousElementSibling; let prevItem = item.previousElementSibling;
if (prevItem) if (prevItem)
return DomHandler.hasClass(prevItem, 'p-disabled') ? this.findPrevItem(prevItem) : prevItem; return DomHandler.hasClass(prevItem, 'p-disabled') || DomHandler.hasClass(prevItem, 'p-multiselect-item-group') ? this.findPrevItem(prevItem) : prevItem;
else else
return null; return null;
}, },
@ -366,23 +417,49 @@ export default {
return !(this.$el.isSameNode(event.target) || this.$el.contains(event.target) || (this.overlay && this.overlay.contains(event.target))); return !(this.$el.isSameNode(event.target) || this.$el.contains(event.target) || (this.overlay && this.overlay.contains(event.target)));
}, },
getLabelByValue(val) { getLabelByValue(val) {
let label = null; let option;
if (this.options) { if (this.options) {
for (let option of this.options) { if (this.optionGroupLabel) {
let optionValue = this.getOptionValue(option); for (let optionGroup of this.options) {
option = this.findOptionByValue(val, this.getOptionGroupChildren(optionGroup));
if(ObjectUtils.equals(optionValue, val, this.equalityKey)) { if (option) {
label = this.getOptionLabel(option);
break; break;
} }
} }
} }
else {
option = this.findOptionByValue(val, this.options);
}
}
return label; return option ? this.getOptionLabel(option): null;
},
findOptionByValue(val, list) {
for (let option of list) {
let optionValue = this.getOptionValue(option);
if(ObjectUtils.equals(optionValue, val, this.equalityKey)) {
return option;
}
}
return null;
}, },
onToggleAll(event) { onToggleAll(event) {
const value = this.allSelected ? [] : this.visibleOptions && this.visibleOptions.map(option => this.getOptionValue(option)); let value = null;
if (this.allSelected) {
value = [];
}
else if (this.visibleOptions) {
if (this.optionGroupLabel) {
value = [];
this.visibleOptions.forEach(optionGroup => value = [...value, ...this.getOptionGroupChildren(optionGroup)]);
}
else {
value = this.visibleOptions.map(option => this.getOptionValue(option));
}
}
this.$emit('update:modelValue', value); this.$emit('update:modelValue', value);
this.$emit('change', {originalEvent: event, value: value}); this.$emit('change', {originalEvent: event, value: value});
@ -421,10 +498,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 [
@ -468,25 +559,54 @@ export default {
}, },
allSelected() { allSelected() {
if (this.filterValue && this.filterValue.trim().length > 0) { if (this.filterValue && this.filterValue.trim().length > 0) {
let allSelected = true; if (this.visibleOptions.length === 0) {
if(this.visibleOptions.length > 0) { return false;
for (let option of this.visibleOptions) { }
if (this.optionGroupLabel) {
for (let optionGroup of this.visibleOptions) {
for (let option of this.getOptionGroupChildren(optionGroup)) {
if (!this.isSelected(option)) { if (!this.isSelected(option)) {
allSelected = false; return false;
break;
} }
} }
} }
else
allSelected = false;
return allSelected;
} }
else { else {
return this.modelValue && this.options && (this.modelValue.length > 0 && this.modelValue.length === this.options.length); for (let option of this.visibleOptions) {
if (!this.isSelected(option)) {
return false;
}
}
}
return true;
}
else {
if (this.modelValue && this.options) {
let optionCount = 0;
if (this.optionGroupLabel)
this.options.forEach(optionGroup => optionCount += this.getOptionGroupChildren(optionGroup).length);
else
optionCount = this.options.length;
return optionCount > 0 && optionCount === this.modelValue.length;
}
return false;
} }
}, },
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: {
@ -568,6 +688,10 @@ export default {
overflow: hidden; overflow: hidden;
} }
.p-multiselect-item-group {
cursor: auto;
}
.p-multiselect-header { .p-multiselect-header {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -34,6 +34,52 @@ data() {
<h5>Placeholder</h5> <h5>Placeholder</h5>
<p>Common pattern is providing an empty option as the placeholder when using native selects, however Dropdown has built-in support using the placeholder option so it is suggested to use it instead of creating an empty option.</p> <p>Common pattern is providing an empty option as the placeholder when using native selects, however Dropdown has built-in support using the placeholder option so it is suggested to use it instead of creating an empty option.</p>
<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>
&lt;Dropdown v-model="selectedGroupedCity" :options="groupedCities"
optionLabel="label" optionGroupLabel="label" optionGroupChildren="items"&gt;
&lt;/Dropdown&gt;
</template>
</code></pre>
<h5>Filtering</h5> <h5>Filtering</h5>
<p>Filtering allows searching items in the list using an input field at the header. In order to use filtering, enable <i>filter</i> property. By default, <p>Filtering allows searching items in the list using an input field at the header. In order to use filtering, enable <i>filter</i> property. By default,
optionLabel is used when searching and <i>filterFields</i> can be used to customize the fields being utilized. Furthermore, <i>filterMatchMode</i> is available optionLabel is used when searching and <i>filterFields</i> can be used to customize the fields being utilized. Furthermore, <i>filterMatchMode</i> is available

View File

@ -38,23 +38,6 @@ data() {
<pre v-code><code> <pre v-code><code>
&lt;Listbox v-model="selectedCity" :options="cities" optionLabel="name" :multiple="true"/&gt; &lt;Listbox v-model="selectedCity" :options="cities" optionLabel="name" :multiple="true"/&gt;
</code></pre>
<h5>Templating</h5>
<p>Label of an option is used as the display text of an item by default, for custom content support define an <i>option</i> template that gets the option instance as a parameter.
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>
&lt;Listbox v-model="selectedCars" :options="cars" :multiple="true" :filter="true" optionLabel="brand" listStyle="max-height:250px" style="width:15em"&gt;
&lt;template #header&gt;&lt;/template&gt;
&lt;template #option="slotProps"&gt;
&lt;div&gt;
&lt;img :alt="slotProps.option.brand" :src="'demo/images/car/' + slotProps.option.brand + '.png'" /&gt;
&lt;span&gt;{{slotProps.option.brand}}&lt;/span&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;template #footer&gt;&lt;/footer&gt;
&lt;/Listbox&gt;
</template>
</code></pre> </code></pre>
<h5>Grouping</h5> <h5>Grouping</h5>
@ -97,9 +80,9 @@ export default {
</code></pre> </code></pre>
<pre v-code><code><template v-pre> <pre v-code><code><template v-pre>
<Listbox v-model="selectedGroupedCity" :options="groupedCities" &lt;Listbox v-model="selectedGroupedCity" :options="groupedCities"
optionLabel="label" optionGroupLabel="label" optionGroupChildren="items"> optionLabel="label" optionGroupLabel="label" optionGroupChildren="items"&gt;
</Listbox> &lt;/Listbox&gt;
</template> </template>
</code></pre> </code></pre>
@ -110,6 +93,23 @@ export default {
<pre v-code><code> <pre v-code><code>
&lt;Listbox v-model="selectedCity" :options="cities" optionLabel="name" :filter="true"/&gt; &lt;Listbox v-model="selectedCity" :options="cities" optionLabel="name" :filter="true"/&gt;
</code></pre>
<h5>Templating</h5>
<p>Label of an option is used as the display text of an item by default, for custom content support define an <i>option</i> template that gets the option instance as a parameter.
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>
&lt;Listbox v-model="selectedCars" :options="cars" :multiple="true" :filter="true" optionLabel="brand" listStyle="max-height:250px" style="width:15em"&gt;
&lt;template #header&gt;&lt;/template&gt;
&lt;template #option="slotProps"&gt;
&lt;div&gt;
&lt;img :alt="slotProps.option.brand" :src="'demo/images/car/' + slotProps.option.brand + '.png'" /&gt;
&lt;span&gt;{{slotProps.option.brand}}&lt;/span&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;template #footer&gt;&lt;/footer&gt;
&lt;/Listbox&gt;
</template>
</code></pre> </code></pre>
<h5>Properties</h5> <h5>Properties</h5>

View File

@ -11,10 +11,20 @@
<div class="content-section implementation"> <div class="content-section implementation">
<div class="card"> <div class="card">
<h5>Basic</h5> <h5>Basic</h5>
<MultiSelect v-model="selectedCities1" :options="cities" optionLabel="name" placeholder="Select a City" /> <MultiSelect v-model="selectedCities1" :options="cities" optionLabel="name" placeholder="Select Cities" />
<h5>Chips</h5> <h5>Chips</h5>
<MultiSelect v-model="selectedCities2" :options="cities" optionLabel="name" placeholder="Select a City" display="chip" /> <MultiSelect v-model="selectedCities2" :options="cities" optionLabel="name" placeholder="Select Cities" display="chip" />
<h5>Grouped</h5>
<MultiSelect v-model="selectedGroupedCities" :options="groupedCities" optionLabel="label" optionGroupLabel="label" optionGroupChildren="items" placeholder="Select Cities">
<template #optiongroup="slotProps">
<div class="p-d-flex p-ai-center country-item">
<img src="../../assets/images/flag_placeholder.png" :class="'flag flag-' + slotProps.option.code.toLowerCase()" width="18" />
<div>{{slotProps.option.label}}</div>
</div>
</template>
</MultiSelect>
<h5>Advanced with Templating and Filtering</h5> <h5>Advanced with Templating and Filtering</h5>
<MultiSelect v-model="selectedCountries" :options="countries" optionLabel="name" placeholder="Select Countries" :filter="true" class="multiselect-custom"> <MultiSelect v-model="selectedCountries" :options="countries" optionLabel="name" placeholder="Select Countries" :filter="true" class="multiselect-custom">
@ -50,6 +60,7 @@ export default {
selectedCities1: null, selectedCities1: null,
selectedCities2: null, selectedCities2: null,
selectedCountries: null, selectedCountries: null,
selectedGroupedCities: null,
cities: [ cities: [
{name: 'New York', code: 'NY'}, {name: 'New York', code: 'NY'},
{name: 'Rome', code: 'RM'}, {name: 'Rome', code: 'RM'},
@ -68,7 +79,34 @@ export default {
{name: 'Japan', code: 'JP'}, {name: 'Japan', code: 'JP'},
{name: 'Spain', code: 'ES'}, {name: 'Spain', code: 'ES'},
{name: 'United States', code: 'US'} {name: 'United States', code: 'US'}
],
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'}
]
}]
} }
}, },
components: { components: {
@ -79,7 +117,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.p-multiselect { .p-multiselect {
min-width: 15rem; width: 18rem;
} }
::v-deep(.multiselect-custom) { ::v-deep(.multiselect-custom) {
@ -101,4 +139,10 @@ export default {
} }
} }
} }
@media screen and (max-width: 640px) {
.p-multiselect {
width: 100%;
}
}
</style> </style>

View File

@ -42,11 +42,68 @@ data() {
</code></pre> </code></pre>
<h5>Custom Content</h5> <h5>Grouping</h5>
<p>Label of an option is used as the display text of an item by default, for custom content support define an <i>option</i> template that gets the option instance as a parameter.</p> <p>Options groups are specified with the <i>optionGroupLabel</i> and <i>optionGroupChildren</i> properties.</p>
<p>In addition the <i>value</i> template can be used to customize the selected values display instead of the default comma separated list.</p> <pre v-code.script><code>
export default {
data() {
return {
selectedGroupedCities: 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>
&lt;MultiSelect v-model="selectedGroupedCities" :options="groupedCities"
optionLabel="label" optionGroupLabel="label" optionGroupChildren="items"&gt;
&lt;/MultiSelect&gt;
</template>
</code></pre>
<h5>Filtering</h5>
<p>Filtering allows searching items in the list using an input field at the header. In order to use filtering, enable <i>filter</i> property. By default,
optionLabel is used when searching and <i>filterFields</i> can be used to customize the fields being utilized. Furthermore, <i>filterMatchMode</i> is available
to define the search algorithm. Valid values are "contains" (default), "startsWith" and "endsWith".</p>
<pre v-code><code>
&lt;MultiSelect v-model="selectedCars" :options="cars" :filter="true" optionLabel="brand" placeholder="Select Brands"/&gt;
</code></pre>
<h5>Templating</h5>
<p>Label of an option is used as the display text of an item by default, for custom content support define an <i>option</i> template that gets the option instance as a parameter.
In addition <i>value</i>, <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> <pre v-code><code><template v-pre>
&lt;MultiSelect v-model="selectedCars2" :options="cars" optionLabel="brand" placeholder="Select a Car"&gt; &lt;MultiSelect v-model="selectedCars2" :options="cars" optionLabel="brand" placeholder="Select a Car"&gt;
&lt;template #header&gt;&lt;/template&gt;
&lt;template #value="slotProps"&gt; &lt;template #value="slotProps"&gt;
&lt;div class="p-multiselect-car-token" v-for="option of slotProps.value" :key="option.brand"&gt; &lt;div class="p-multiselect-car-token" v-for="option of slotProps.value" :key="option.brand"&gt;
&lt;img :alt="option.brand" :src="'demo/images/car/' + option.brand + '.png'" /&gt; &lt;img :alt="option.brand" :src="'demo/images/car/' + option.brand + '.png'" /&gt;
@ -62,15 +119,9 @@ data() {
&lt;span&gt;{{slotProps.option.brand}}&lt;/span&gt; &lt;span&gt;{{slotProps.option.brand}}&lt;/span&gt;
&lt;/div&gt; &lt;/div&gt;
&lt;/template&gt; &lt;/template&gt;
&lt;template #footer&gt;&lt;/template&gt;
&lt;/MultiSelect&gt; &lt;/MultiSelect&gt;
</template> </template>
</code></pre>
<h5>Filter</h5>
<p>Filtering allows searching items in the list using an input field at the header. In order to use filtering, enable the <i>filter</i> property.</p>
<pre v-code><code>
&lt;MultiSelect v-model="selectedCars" :options="cars" :filter="true" optionLabel="brand" placeholder="Select Brands"/&gt;
</code></pre> </code></pre>
<h5>Properties</h5> <h5>Properties</h5>
@ -116,12 +167,54 @@ data() {
<td>null</td> <td>null</td>
<td>Property name or getter function to use as the disabled flag of an option, defaults to false when not defined.</td> <td>Property name or getter function to use as the disabled flag of an option, defaults to false when not defined.</td>
</tr> </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> <tr>
<td>scrollHeight</td> <td>scrollHeight</td>
<td>string</td> <td>string</td>
<td>200px</td> <td>200px</td>
<td>Height of the viewport in pixels, a scrollbar is defined if height of list exceeds this value.</td> <td>Height of the viewport in pixels, a scrollbar is defined if height of list exceeds this value.</td>
</tr> </tr>
<tr>
<td>filter</td>
<td>boolean</td>
<td>false</td>
<td>When specified, displays a filter input at header.</td>
</tr>
<tr>
<td>filterPlaceholder</td>
<td>string</td>
<td>null</td>
<td>Placeholder text to show when filter input is empty.</td>
</tr>
<tr>
<td>filterLocale</td>
<td>string</td>
<td>undefined</td>
<td>Locale to use in filtering. The default locale is the host environment's current locale.</td>
</tr>
<tr>
<td>filterMatchMode</td>
<td>string</td>
<td>contains</td>
<td>Defines the filtering algorithm to use when searching the options. Valid values are "contains" (default), "startsWith" and "endsWith"</td>
</tr>
<tr>
<td>filterFields</td>
<td>array</td>
<td>null</td>
<td>Fields used when filtering the options, defaults to optionLabel.</td>
</tr>
<tr> <tr>
<td>placeholder</td> <td>placeholder</td>
<td>string</td> <td>string</td>
@ -134,12 +227,6 @@ data() {
<td>false</td> <td>false</td>
<td>When present, it specifies that the component should be disabled.</td> <td>When present, it specifies that the component should be disabled.</td>
</tr> </tr>
<tr>
<td>filter</td>
<td>boolean</td>
<td>false</td>
<td>When specified, displays an input field to filter the items on keyup.</td>
</tr>
<tr> <tr>
<td>tabindex</td> <td>tabindex</td>
<td>string</td> <td>string</td>
@ -158,18 +245,6 @@ data() {
<td>null</td> <td>null</td>
<td>A property to uniquely identify an option.</td> <td>A property to uniquely identify an option.</td>
</tr> </tr>
<tr>
<td>filterPlaceholder</td>
<td>string</td>
<td>null</td>
<td>Placeholder text to show when filter input is empty.</td>
</tr>
<tr>
<td>filterLocale</td>
<td>string</td>
<td>undefined</td>
<td>Locale to use in filtering. The default locale is the host environment's current locale.</td>
</tr>
<tr> <tr>
<td>ariaLabelledBy</td> <td>ariaLabelledBy</td>
<td>string</td> <td>string</td>
@ -186,7 +261,13 @@ data() {
<td>emptyFilterMessage</td> <td>emptyFilterMessage</td>
<td>string</td> <td>string</td>
<td>No results found</td> <td>No results found</td>
<td>Text to display when filtering does not return any results.</td> <td>Text to display when filtering does not return any results. Defaults to value from PrimeVue locale configuration.</td>
</tr>
<tr>
<td>emptyMessage</td>
<td>string</td>
<td>No results found</td>
<td>Text to display when there are no options available. Defaults to value from PrimeVue locale configuration.</td>
</tr> </tr>
<tr> <tr>
<td>display</td> <td>display</td>
@ -270,6 +351,53 @@ data() {
</table> </table>
</div> </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>option</td>
<td>option: Option instance <br />
index: Index of the option</td>
</tr>
<tr>
<td>optiongroup</td>
<td>option: OptionGroup instance <br />
index: Index of the option group</td>
</tr>
<tr>
<td>value</td>
<td>value: Value of the component <br />
placeholder: Placeholder prop value</td>
</tr>
<tr>
<td>header</td>
<td>value: Value of the component <br />
options: Displayed options</td>
</tr>
<tr>
<td>footer</td>
<td>value: Value of the component <br />
options: Displayed options</td>
</tr>
<tr>
<td>emptyfilter</td>
<td>-</td>
</tr>
<tr>
<td>empty</td>
<td>-</td>
</tr>
</tbody>
</table>
</div>
<h5>Styling</h5> <h5>Styling</h5>
<p>Following is the list of structural style classes, for theming classes visit <router-link to="/theming">theming</router-link> page.</p> <p>Following is the list of structural style classes, for theming classes visit <router-link to="/theming">theming</router-link> page.</p>
<div class="doc-tablewrapper"> <div class="doc-tablewrapper">
@ -331,10 +459,20 @@ data() {
<pre v-code><code><template v-pre> <pre v-code><code><template v-pre>
&lt;h5&gt;Basic&lt;/h5&gt; &lt;h5&gt;Basic&lt;/h5&gt;
&lt;MultiSelect v-model="selectedCities1" :options="cities" optionLabel="name" placeholder="Select a City" /&gt; &lt;MultiSelect v-model="selectedCities1" :options="cities" optionLabel="name" placeholder="Select Cities" /&gt;
&lt;h5&gt;Chips&lt;/h5&gt; &lt;h5&gt;Chips&lt;/h5&gt;
&lt;MultiSelect v-model="selectedCities2" :options="cities" optionLabel="name" placeholder="Select a City" display="chip"/&gt; &lt;MultiSelect v-model="selectedCities2" :options="cities" optionLabel="name" placeholder="Select Cities" display="chip" /&gt;
&lt;h5&gt;Grouped&lt;/h5&gt;
&lt;MultiSelect v-model="selectedGroupedCities" :options="groupedCities" optionLabel="label" optionGroupLabel="label" optionGroupChildren="items" placeholder="Select Cities"&gt;
&lt;template #optiongroup="slotProps"&gt;
&lt;div class="p-d-flex p-ai-center country-item"&gt;
&lt;img src="../../assets/images/flag_placeholder.png" :class="'flag flag-' + slotProps.option.code.toLowerCase()" width="18" /&gt;
&lt;div&gt;{{slotProps.option.label}}&lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;/MultiSelect&gt;
&lt;h5&gt;Advanced with Templating and Filtering&lt;/h5&gt; &lt;h5&gt;Advanced with Templating and Filtering&lt;/h5&gt;
&lt;MultiSelect v-model="selectedCountries" :options="countries" optionLabel="name" placeholder="Select Countries" :filter="true" class="multiselect-custom"&gt; &lt;MultiSelect v-model="selectedCountries" :options="countries" optionLabel="name" placeholder="Select Countries" :filter="true" class="multiselect-custom"&gt;
@ -364,6 +502,7 @@ export default {
selectedCities1: null, selectedCities1: null,
selectedCities2: null, selectedCities2: null,
selectedCountries: null, selectedCountries: null,
selectedGroupedCities: null,
cities: [ cities: [
{name: 'New York', code: 'NY'}, {name: 'New York', code: 'NY'},
{name: 'Rome', code: 'RM'}, {name: 'Rome', code: 'RM'},
@ -382,7 +521,34 @@ export default {
{name: 'Japan', code: 'JP'}, {name: 'Japan', code: 'JP'},
{name: 'Spain', code: 'ES'}, {name: 'Spain', code: 'ES'},
{name: 'United States', code: 'US'} {name: 'United States', code: 'US'}
],
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'}
]
}]
} }
} }
} }
@ -432,16 +598,26 @@ export default {
<div class="content-section implementation"> <div class="content-section implementation">
<div class="card"> <div class="card">
<h5>Basic</h5> <h5>Basic</h5>
<MultiSelect v-model="selectedCities1" :options="cities" optionLabel="name" placeholder="Select a City" /> <MultiSelect v-model="selectedCities1" :options="cities" optionLabel="name" placeholder="Select Cities" />
<h5>Chips</h5> <h5>Chips</h5>
<MultiSelect v-model="selectedCities2" :options="cities" optionLabel="name" placeholder="Select a City" display="chip" /> <MultiSelect v-model="selectedCities2" :options="cities" optionLabel="name" placeholder="Select Cities" display="chip" />
<h5>Grouped</h5>
<MultiSelect v-model="selectedGroupedCities" :options="groupedCities" optionLabel="label" optionGroupLabel="label" optionGroupChildren="items" placeholder="Select Cities">
<template #optiongroup="slotProps">
<div class="p-d-flex p-ai-center country-item">
<img src="../../assets/images/flag_placeholder.png" :class="'flag flag-' + slotProps.option.code.toLowerCase()" width="18" />
<div>{{slotProps.option.label}}</div>
</div>
</template>
</MultiSelect>
<h5>Advanced with Templating and Filtering</h5> <h5>Advanced with Templating and Filtering</h5>
<MultiSelect v-model="selectedCountries" :options="countries" optionLabel="name" placeholder="Select Countries" :filter="true" class="multiselect-custom"> <MultiSelect v-model="selectedCountries" :options="countries" optionLabel="name" placeholder="Select Countries" :filter="true" class="multiselect-custom">
<template #value="slotProps"> <template #value="slotProps">
<div class="country-item country-item-value" v-for="option of slotProps.value" :key="option.code"> <div class="country-item country-item-value" v-for="option of slotProps.value" :key="option.code">
<img src="https://www.primefaces.org/wp-content/uploads/2020/05/placeholder.png" /> <img src="../../assets/images/flag_placeholder.png" :class="'flag flag-' + option.code.toLowerCase()" />
<div>{{option.name}}</div> <div>{{option.name}}</div>
</div> </div>
<template v-if="!slotProps.value || slotProps.value.length === 0"> <template v-if="!slotProps.value || slotProps.value.length === 0">
@ -450,7 +626,7 @@ export default {
</template> </template>
<template #option="slotProps"> <template #option="slotProps">
<div class="country-item"> <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.option.code.toLowerCase()" />
<div>{{slotProps.option.name}}</div> <div>{{slotProps.option.name}}</div>
</div> </div>
</template> </template>
@ -467,6 +643,7 @@ export default {
selectedCities1: null, selectedCities1: null,
selectedCities2: null, selectedCities2: null,
selectedCountries: null, selectedCountries: null,
selectedGroupedCities: null,
cities: [ cities: [
{name: 'New York', code: 'NY'}, {name: 'New York', code: 'NY'},
{name: 'Rome', code: 'RM'}, {name: 'Rome', code: 'RM'},
@ -485,13 +662,40 @@ export default {
{name: 'Japan', code: 'JP'}, {name: 'Japan', code: 'JP'},
{name: 'Spain', code: 'ES'}, {name: 'Spain', code: 'ES'},
{name: 'United States', code: 'US'} {name: 'United States', code: 'US'}
],
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'}
]
}]
} }
} }
}`, }`,
style: `<style lang="scss" scoped> style: `<style lang="scss" scoped>
.p-multiselect { .p-multiselect {
min-width: 15rem; width: 18rem;
} }
::v-deep(.multiselect-custom) { ::v-deep(.multiselect-custom) {
@ -507,11 +711,16 @@ export default {
margin-right: .5rem; margin-right: .5rem;
background-color: var(--primary-color); background-color: var(--primary-color);
color: var(--primary-color-text); color: var(--primary-color-text);
img.flag {
width: 17px;
}
}
} }
img { @media screen and (max-width: 640px) {
width: 17px; .p-multiselect {
margin-right: 0.5rem; width: 100%;
} }
} }
</style>` </style>`