DEV Community

Claude Biver
Claude Biver

Posted on

Vuetify: Sorting data for a v-select component and adding a horizontal line

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;
  };
}
Enter fullscreen mode Exit fullscreen mode

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'];
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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),
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)