Image

build-a-gmail-clone-with-vue-3

4 min read
Last update: December 19, 2021

Bulk Actions

For the starting code for this lesson, switch to branch: https://github.com/Code-Pop/build-gmail-clone-with-vue-3/tree/video-14-start

In this lesson we’re going to add a set of buttons in the Bulk Action Bar that, when clicked, will perform an action on all selected emails.

First, let’s get fix some styling. We’ll remove the h1 from App.vue, then we’ll style what we have in our BulkActionBar.vue component.

<div class="bulk-action-bar">
    <span class="checkbox">
        <input type="checkbox" :checked="allEmailsSelected" :class="[someEmailsSelected ? 'partial-check' : '']" @click="bulkSelect" />
    </span>
    <span class="buttons">
        <button>Click Me</button>
    </span>
</div>

If you’re following along with the starter code, the CSS should already be in your app. If you’re following from scratch, here it is:

button {
font-size: 16px;
padding: 8px;
border-radius: 3px;
margin: 5px 10px 5px 0px;
cursor: pointer;
}

button:disabled {
cursor: auto;
}

button.selected {
cursor: auto;
color: black;
border-color: black;
border-width: 2px;
}

.bulk-action-bar {
width: 100%;
max-width: 1000px;
margin: auto;
text-align: left;
padding-bottom: 8px;
}

.bulk-action-bar input {
margin: 5px;
}

.bulk-action-bar .checkbox {
margin-right: 6px;
margin-left: 3px;
}

https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F14-1.opt.jpg?alt=media&token=16105146-5d82-4522-ba47-5e66b4cb8a0d

Now that it looks much better, let’s create those buttons:

<div class="bulk-action-bar">
    <span class="checkbox">
        <input type="checkbox" :checked="allAreSelected" :class="[partialSelection ? 'partial-check' : '']" @click="bulkSelect">
    </span>

    <span class="buttons">
        <button @click="emailSelection.markRead()">
            Mark Read
        </button>
        <button @click="emailSelection.markUnread()"">
        Mark Unread
      </button>
      <button @click=" emailSelection.archive()">
            Archive
        </button>
    </span>
</div>

We’ve added three buttons, each of which have a different function called on the emailSelection reactive object. We’ll add the markRead function to emailSelection first.

import axios from 'axios';
//...
const markRead = () => {
emails.forEach(email => {
email.read = true
axios.put(`http://localhost:3000/emails/${email.id}`, email)
})
};

In order to make this work, we’ll need to have our emails array in MailTable.vue reactive, so be sure to fix that.

Then we’ll have our markUnread and archive functions.

import axios from 'axios';
//...
const markUnread = () => {
emails.forEach(email => {
email.read = false
axios.put(`http://localhost:3000/emails/${email.id}`, email)
})
};
const archive = () => {
emails.forEach(email => {
email.archived = true
axios.put(`http://localhost:3000/emails/${email.id}`, email)
})
};

You’ll notice how similar they are to markRead… we can abstract a lot of that shared code.

import axios from 'axios';

//...
const forSelected = (fn) => {
emails.forEach(email => {
fn(email)
axios.put(`http://localhost:3000/emails/${email.id}`, email)
})
};
const markRead = () => forSelected(e => e.read = true )
const markUnread = () => forSelected(e => e.read = false )
const archive = () => { forSelected(e => e.archived = true); clear()}

return {
emails,
toggle,
addMultiple,
clear,
markRead,
markUnread,
archive
}

Notice that we have a forSelected function — one that is only used in the composition function, and not returned — that handles looping through the emails, calling the given function for each, then saving it. Then in the other three new functions, they call forSelected with the function to be called on each selected email.

We also added a call to clear to archive , since they’ll no longer be on that screen.

In production, it may be worthwhile to make the save functionality happen outside the loop in order to improve performance and reduce number of API calls.

Now let’s make it so that the buttons are disabled when clicking them won’t do anything. All are disabled when no emails are selected, and mark read/mark unread are disabled when all selected emails are read/unread.

Because emails are a set, we must turn them into an Array before using every. We can use Array.from(emailSelection.emails) or the shorter [...emailSelection.emails].

<span class="buttons">
    <button @click="emailSelection.markRead()" :disabled="[...emailSelection.emails].every(e => e.read)">
        Mark Read
    </button>
    <button @click="emailSelection.markUnread()" :disabled="[...emailSelection.emails].every(e => !e.read)">
        Mark Unread
    </button>
    <button @click="emailSelection.archive()" :disabled="numberSelected == 0">
        Archive
    </button>
</span>

Now we have a really nice screen with lots of great interaction options!

https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F14-2.opt.jpg?alt=media&token=76e32e3d-176a-47f3-bef2-40be4373bc6b

For the ending code for this lesson, switch to branch: https://github.com/Code-Pop/build-gmail-clone-with-vue-3/tree/video-14-end

Jeffrey teaches an 8-week beginning Vue course with guaranteed results. Built on top of the VueMastery curriculum, with extra hands-on assignments and personal attention. You can find it here.