How to make an Avatar Picker with Vuetify?

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.

List all files from a directory with require.context in Javascript

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