Skip to content

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.

<template>
    <v-container fluid>
        <v-layout column>
            <v-card>
                <v-card-text>
                    <v-flex class="mb-4">
                        <v-avatar size="96" class="mr-4">
                            <img :src="'/avatars/avatar_' + (form.avatar.toLowerCase()) + '.png'" alt="Avatar">
                        </v-avatar>
                        <v-btn @click="openAvatarPicker">Change Avatar</v-btn>
                    </v-flex>
                    <v-text-field
                        v-model="form.firstName"
                        label="FirstName"></v-text-field>
                    <v-text-field
                        v-model="form.lastName"
                        label="Last Name"></v-text-field>
                    <v-text-field
                        v-model="form.contactEmail"
                        label="Email Address"></v-text-field>
                </v-card-text>
                <v-card-actions>
                    <v-btn color="primary" :loading="loading" @click.native="update">
                        <v-icon left dark>check</v-icon>
                        Save Changes
                    </v-btn>
                </v-card-actions>
            </v-card>
        </v-layout>
        <avatar-picker
            v-model="showAvatarPicker"
            :current-avatar="form.avatar"
            @selected="selectAvatar"></avatar-picker>
    </v-container>
</template>

<script>
    import AvatarPicker from '~/components/AvatarPicker'

    export default {
        pageTitle: 'My Profile',

        components: { AvatarPicker },

        data () {
            return {
                loading: false,
                form: {
                    firstName: 'John',
                    lastName: 'Doe',
                    contactEmail: 'john@doe.com',
                    avatar: 'MALE_CAUCASIAN_BLOND_BEARD'
                },
                showAvatarPicker: false
            }
        },

        methods: {
            openAvatarPicker () {
                this.showAvatarPicker = true
            },

            selectAvatar (avatar) {
                this.form.avatar = avatar
            }
        }
    }
</script>

View the original gist

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