I recently needed to implement a select component in Vuetify for a list of countries, which has a couple of countries at the top at the list, divided with a horizontal line. It took me a while to figure out how to do this, so I figured I’d share in case someone else needs to do the same.
Sorting the data
In my example I used an interface for the Countries that allows for multiple translations to be saved as well:
export interface Country {
id: string;
translations: {
[key: string]: string;
};
}
Before rendering the data in the <v-select>
component, the array of countries needed some sorting. To manage the highlighted countries, you can either fetch this data from a backend,or — so simplicity’s sake — use a const
:
export const preferredCountryOrder = ['AT', 'CZ', 'DE', 'HU', 'SI'];
I then used a basic sorting algorithm that compares two countries based on their id property.
More specifically, the algorithm checks
- if both countries are part of the
preferredCountryOrder
array, and if so, sorts it relative to each other based on their id; - If only one of the countries are part of the
preferredCountryOrder
array, it should be prioritized; - And, finally, if none of the two compared countries are in the
preferredCountryOrderarray
, the original order should be kept.
This logic translates to this Typescript code:
function sortCountries(a: Country, b: Country): number {
const indexA = preferredCountryOrder.indexOf(a.id);
const indexB = preferredCountryOrder.indexOf(b.id);
// If both countries are in the preferred order,
// sort them by their index in the preferred order
if (indexA !== -1 && indexB !== -1) {
return indexA - indexB;
}
// If only one of the countries is in the preferred order,
// prioritize it
if (indexA !== -1) {
return -1;
}
if (indexB !== -1) {
return 1;
}
// If neither country is in the preferred order,
// maintain their original order
return 0;
}
Finally, we can apply the sorting algorithm to our data. In my case, I use useQuery to fetch data from the backend, but it doesn’t really matter where your data is coming from, it could also come from a static array.
const { data: countries, isLoading: countriesAreLoading } = useQuery({
queryKey: [Resources.Countries],
queryFn: () => fetchAll<Country>(URL),
select: (data) => data.sort(sortCountries),
});
Adding the sorted array to the select component
Now that our data is sorted, we can add it to our <v-select>
component. This is relatively straightforward, as we can just pass it as items prop:
<template>
<v-select
:items="countries ?? []"
>
</v-select>
</template>
However, the sorted countries on top should be separated with a horizontal line. Luckily, Vuetify lets us directly use the slots so we can modify the items in the v-select component.
To do so, we use <template #item="{ props, index}">
inside the <v-select>
component. It allows us to directly target the list item and modify it. We get the props and the index, which hold the data of the list item and the index of the list item.
We can render the <v-select>
component and bind the <v-list-item>
to the props:
<template>
<v-select
:items="countries ?? []"
>
<template #item="{ props, index }">
<v-list-item v-bind="props" />
</template>
</v-select>
</template>
This basically doesn’t change anything to how the <v-select>
would’ve been rendered by Vuetify in the first place, but it allows us to change the list item.
We then want to check if the rendered list item is still part of the preferredCountryOrder
array, and if it’s not, we can render a horizontal line. The easiest way to do this is to check if the index of the rendered list item equals the length of the the preferredCountryOrder
array. It that’s the case, the rendered list item is the first item that’s not highlighted (the index starts at 0).
In code, it would look like this:
<v-divider v-if="index === preferredCountryOrder.length" />
Adding it all together:
<template>
<v-select
:items="countries ?? []"
>
<template #item="{ props, index }">
<v-divider v-if="index === preferredCountryOrder.length" />
<v-list-item v-bind="props" />
</template>
</v-select>
</template>
Conclusion
slots in vuetify can be very powerful, but aren’t always fully documented. In this case, we overwrote the list items of a <v-select>
and added some custom logic to it. By using v-bind="props"
on the , we ensure all the necessary data is passed directly to the component, as we don’t need to modify the data itself.
I hope this is useful and saves you some time. Happy coding!
Top comments (0)