Today I was building a profile page for a client’s app. The form has classic fields like first name, last name, email address, etc.. but also an “Avatar” field.
The Avatar must be picked amongst a few avatars that I uploaded in a static folder.
I want my app to list all those files and render them as a list of avatar components and when the user clicks on one of the avatar it should update the selected avatar.
Here’s what I started with. A simple Profile form with all the fields, the current avatar and a button “Change Avatar”.
When the user clicks on “Change Avatar” I want to open a dialog with all the available avatars. When the user clicks an avatar it should close the dialog & replace the current avatar with the new one.
Here’s the code for this view. I’m using Nuxt by the way.
Now let’s create an AvatarPicker component. As I said, this will be a dialog so we can start by creating a dialog component.
<template> <v-dialog :fullscreen="$vuetify.breakpoint.xs" width="500" transition="dialog-bottom-transition" v-model="show"> <v-card> <v-toolbar dark color="primary"> <v-btn icon dark @click="show = false"> <v-icon>close</v-icon> </v-btn> <v-toolbar-title>Select an Avatar</v-toolbar-title> <v-spacer></v-spacer> </v-toolbar> <v-layout> ... Avatars will be here </v-layout> </v-card> </v-dialog> </template>
Now this component will accept 2 props: `currentAvatar` which will let us style the current avatar as we want and `value` which will make the dialog open or closed.
export default { props: { currentAvatar: { type: String, required: true }, value: Boolean } }
Now we need to take care of the `show` property that’s used as the dialog’s v-model.
export default { ..., computed: { show: { get () { return this.value }, set (value) { this.$emit('input', value) } } } }
This is a trick that makes it possible to open a dialog component from another component. Here’s more info.
Now we need to list all avatars from my `~/static/avatars/` directory. For this I found a useful snippet in the Laravel Framework. They use it for auto registering all components.
I am not sure if this is a really good method, but I find it useful and it works. Feel free to PM me if you have a better alternative.
So basically here’s how it works:
let files = require.context('~/static/avatars', true, /\.png$/i)
files.keys()
returns an array of all the files found in the directory we passed (~/static/avatars
) if we console.dir
the result we should see something like this.
Now that we have an array, we can easily work with it to return the results we want.
In my case I want an `avatars` object that is formatted like this:
let avatars = { 'FEMALE_AFRO_AMERICAN_BLACK_ATTACHED_HAIR': { id: 'FEMALE_AFRO_AMERICAN_BLACK_ATTACHED_HAIR', path: 'avatar_female_afro_american_black_attached_hair.png' }, ... }
This way in my database, my user will have an avatar value of FEMALE_AFRO_AMERICAN_BLACK_ATTACHED_HAIR
and I’ll be able to retrieve it’s path using something like avatars[profile.avatar].path
.
Let’s do it. We work a little on the key to generate the id and the path.
let avatars = {} let files = require.context('~/static/avatars', true, /\.png$/i) files.keys().map((key) => { let id = key.split('/').pop().split('.')[0].substring(7).toUpperCase() avatars[id] = { path: key.split('/').pop(), id: id } })
If you console.table the avatars
you should see something like this.
Now that we have the avatars, let’s display them in the dialog.
<v-layout row wrap> <v-flex v-for="avatar in avatars" :key="avatar.id" xs4 sm3 d-flex> <v-card tile flat class="d-flex"> <v-card-text class="d-flex"> <v-avatar size="96" @click="selectAvatar(avatar)" class="avatar-picker-avatar" :class="{ 'current': avatar.id === currentAvatar }"> <img :src="'/avatars/' + (avatar.path)"> </v-avatar> </v-card-text> </v-card> </v-flex> </v-layout>
Now it should look like this.
Now we just need to add the logic to replace the current avatar.
First in the AvatarPicker.vue we’ll add the `selectAvatar` method that’ll emit the `selected` event.
export default { ..., methods: { selectAvatar (avatar) { this.$emit('selected', avatar.id) this.show = false } } }
In the profile.vue
page we’ll now catch this event and replace the avatar in the form.
export default { ..., methods: { selectAvatar (avatar) { this.form.avatar = avatar } } }
VoilĂ !
You can check out the full code on this gist.
https://gist.github.com/depsimon/66edd89939f6700c50b5946d4e129460