left-icon

Nuxt.js Succinctly®
by Ed Freitas

Previous
Chapter

of
A
A
A

CHAPTER 4

Client Logic

Client Logic


Overview

With most of the application's UI done, it's now time to focus on the application's client-side logic. I'll mainly use the Options API from Vue.js rather than the Composition API (new with Vue.js 3), which uses the setup method.

The reason is that I don't want to introduce too many changes for developers coming from Vue.js version 2, so we can focus on Nuxt.js rather than also having to focus on different Vue.js 3 changes.

Add to favorites method

The first step in adding logic to our application is to add a method that allows us to add a book to the list of favorite books. The following listing shows the method that achieves that, called addToFavs.

Code Listing 4-a: Methods (index.vue)

<script>

    export default {

        data() {

            return {

                favorites: [],

                books: [

                ...

                ]

            }

        },

        methods: {

            addToFavs(book) {

                let inFavs = false;

                for (const fav of this.favorites) {

                    if (fav.uuid === book.uuid) {

                        inFavs = true;

                        break;

                    }

                }

                if (!inFavs) {

                    this.favorites.push({...book});

                }

            }

        }

    }

</script>

For the sake of readability, I've explicitly excluded the book data (which is why you'll notice this instead ...). However, that doesn't mean that this data has been removed from the code.

Let's now explore what the addToFavs method does. The addToFavs method begins by declaring an inFavs variable initialized to false.

We assume that no books have been added to the list of favorites at this stage. Notice that the list of favorite books is initialized as an empty array within the data method (favorites: []).

Following that, we loop through the favorites array (using a for loop) and check if the current item (fav) on the favorites list has an uuid equal to the uuid of the book being inserted into the favorites list.

If the uuid of the current item has the same value as the uuid of the book being inserted, then inFavs is set to true, and the loop finalizes.

If the book being inserted does not exist on the list of favorites, then it is added to the list with this instruction: this.favorites.push({...book}).

Note: We are using the JavaScript spread syntax (…book) when calling the push method of the favorites array, as we want to add all the properties of the book object as one element of the array.

By doing this, we ensure that the same book is not added more than once to the favorites list.

Click event binding

Now that we have the addToFavs method ready, let's enable it by binding it to a click event, as seen in bold in the following code.

Code Listing 4-b: Binding the addToFavs Methods (index.vue)

<button @click="addToFavs(book)" class="btn btn-outline-primary">

    Add to Favorites

</button>

This works because we use the v-on directive (shorthanded as @) to bind the addToFavs method to the click event. To the addToFavs method, we pass the book that we want to insert to the favorites list. Here is the updated code for index.vue.

Code Listing 4-c: Updated index.vue

<template>

    <div class="container">

        <br />

        <div class="row">

            <div v-for="(book, index) in books"

                :key="book.uuid + '_' + index"

                class="col-md-4"

            >

                <div class="card-mb-3">

                    <a target="_blank" :href="book.url">

                        <img :src="book.picUrl" class="card-img-top">

                    </a>

                    <div class="card-body">

                        <h5 class="card-title">

                            {{ book.name }}

                        </h5>

                        <p class="card-text">

                            {{ book.description }}

                        </p>

                        <p class="card-text">

                            {{ book.author }}

                        </p>

                        <div class="d-grid">

                            <button @click="addToFavs(book)"

                                class="btn btn-outline-primary">

                                Add to Favorites

                            </button>

                        </div>

                    </div>

                </div>

            </div>

        </div>

    </div>

</template>

<style scoped>

    .img {

        width: 80%;

        height: auto;

    }

    .center {

        display: block;

        margin-left: auto;

        margin-right: auto;

        width: 100%;

    }

</style>

<script>

    export default {

        data() {

            return {

                favorites: [],

                books: [

                    {

                        uuid: 'cde4664e-afb8-47b2-b4e0-ecedad4ebbf6',

                        name: 'Razor Components Succinctly',

                        description: 'Razor components are specific

                          building blocks within the Blazor framework.',

                        author: 'Ed Freitas',

                        url: 'https://www.syncfusion.com/succinctly-free-

                              ebooks/razor-components-succinctly',

                        picUrl: 

                          'https://cdn.syncfusion.com/content/images/

                           downloads/ebook/ebook-cover/

                           Razor-Components-Succinctly.png'

                    },

                    {

                        uuid: 'e3d063c7-57bf-4df6-a626-137bfc658e04',

                        name: 'Azure Virtual Desktop Succinctly',

                        description: 'Put simply, Azure Virtual Desktop  

                                      is a way to serve Windows

                                      resources over the internet.',

                        author: 'Marco Moioli',

                        url: 'https://www.syncfusion.com/succinctly-free-

                              ebooks/azure-virtual-desktop-succinctly',

                        picUrl:  

                             'https://cdn.syncfusion.com/content/images/

                              downloads/ebook/ebook-cover/

                              azure-virtual-desktop-succinctly.png'

                    },

                    {

                        uuid: '86af8093-7ba9-4178-9c19-0eeb9e75bcf0',

                        name: 'Ansible Succinctly',

                        description: 'Ansible is an open-source software,

                          automation engine, and automation language.',

                        author: 'Zoran Maksimovic',

                        url: 'https://www.syncfusion.com/succinctly-free-

                          ebooks/ansible-succinctly',

                        picUrl:         

                          'https://cdn.syncfusion.com/content/images/

                           downloads/ebook/ebook-cover/

                           ansible-succinctly.png'

                    }

                ]

            }

        },

        methods: {

            addToFavs(book) {

                let inFavs = false;

                for (const fav of this.favorites) {

                    if (fav.uuid === book.uuid) {

                        inFavs = true;

                        break;

                    }

                }

                if (!inFavs) {

                    this.favorites.push({...book});

                }

            }

        }

    }

</script>

Note: I've written some of the strings in multiple lines per attribute (for readability); however, if you paste these in your index.vue file, do not split these into numerous lines—make sure each value remains in a single line.

Now we’re ready to work on the FavList.vue file.

Moving the Favorites list

As you have noticed, the favorites array has been declared within the data method of the index.vue file (as seen previously).

Although this has worked so far, it poses a problem to us going forward. We need to have the data contained within the favorites array available within the FavList.vue file.

We could pass the favorites array to the FavList.vue file by calling the component FavList and passing the favorites array as a property: <FavList favs="favorites"/>.

This could work, but the problem with this approach is that besides repeating the data, we would have one copy within index.vue and another copy in FavList.vue—which we would also need to keep in sync.

A better way would be to move the favorites array to FavList.vue and then reference the favorites array from index.vue. By doing this, we keep one copy of the data.

We can do this by changing the name of the favorites array to favlist (to be declared in FavList.vue) and then referencing it within index.vue as <FavList ref="favlist"/>.

Refactoring index.vue

To begin, let's refactor index.vue (which no longer uses the favorites array) as follows. The newest changes are emphasized in bold.

Code Listing 4-d: Refactored index.vue

<template>

    <div class="container">

        <FavList ref="favlist"/>

        <br />

        <div class="row">

            <div v-for="(book, index) in books"

                 :key="book.uuid + '_' + index"

                class="col-md-4"

            >

                <div class="card-mb-3">

                    <a target="_blank" :href="book.url">

                        <img :src="book.picUrl" class="card-img-top">

                    </a>

                    <div class="card-body">

                        <h5 class="card-title">

                            {{ book.name }}

                        </h5>

                        <p class="card-text">

                            {{ book.description }}

                        </p>

                        <p class="card-text">

                            {{ book.author }}

                        </p>

                        <div class="d-grid">

                            <button @click="addToFavs(book)"

                                class="btn btn-outline-primary">

                                Add to Favorites

                            </button>

                        </div>

                    </div>

                </div>

            </div>

        </div>

    </div>

</template>

<style scoped>

    .img {

        width: 80%;

        height: auto;

    }

    .center {

        display: block;

        margin-left: auto;

        margin-right: auto;

        width: 100%;

    }

</style>

<script>

    import FavList from '../components/FavList';

    export default {

        components: { FavList },

        data() {

            return {

                books: [

                    {

                        uuid: 'cde4664e-afb8-47b2-b4e0-ecedad4ebbf6',

                        name: 'Razor Components Succinctly',

                        description: 'Razor components are specific

                          building blocks within the Blazor framework.',

                        author: 'Ed Freitas',

                        url: 'https://www.syncfusion.com/succinctly-free-

                              ebooks/razor-components-succinctly',

                        picUrl: 

                             'https://cdn.syncfusion.com/content/images/

                              downloads/ebook/ebook-cover/

                              Razor-Components-Succinctly.png'

                    },

                    {

                        uuid: 'e3d063c7-57bf-4df6-a626-137bfc658e04',

                        name: 'Azure Virtual Desktop Succinctly',

                        description: 'Put simply, Azure Virtual Desktop   

                                      is a way to serve Windows

                                      resources over the internet.',

                        author: 'Marco Moioli',

                        url: 'https://www.syncfusion.com/succinctly-free-

                              ebooks/azure-virtual-desktop-succinctly',

                        picUrl: 'https://cdn.syncfusion.com/content/

                                 images/downloads/ebook/ebook-cover/

                                 azure-virtual-desktop-succinctly.png'

                    },

                    {

                        uuid: '86af8093-7ba9-4178-9c19-0eeb9e75bcf0',

                        name: 'Ansible Succinctly',

                        description: 'Ansible is an open-source software,

                          automation engine, and automation language.',

                        author: 'Zoran Maksimovic',

                        url: 'https://www.syncfusion.com/succinctly-free-

                              ebooks/ansible-succinctly',

                        picUrl: 'https://cdn.syncfusion.com/content/

                                 images/downloads/ebook/

                                 ebook-cover/ansible-succinctly.png'

                    }

                ]

            }

        },

        methods: {

            addToFavs(book) {

                let inFavs = false;

                const favs = this.$refs.favlist;

                for (const fav of favs.favorites) {

                    if (fav.uuid === book.uuid) {

                        inFavs = true;

                        break;

                    }

                }

                if (!inFavs) {

                    favs.favorites.push({...book});

                }

            }

        }

    }

</script>

Note: I've written some of the strings in multiple lines per attribute (for readability); however, if you paste these in your index.vue file, do not split these into numerous lines—make sure each value remains in a single line.

Let's go over the changes. First, we've included (within the HTML markup) a reference to the FavList component as follows <FavList ref="favlist"/>.

Notice that favlist (which is the original favorites array renamed) is being passed as reference (ref). This means that although it is part of FavList.vue (as we will see shortly), we can access and manipulate it from index.vue.

Next, we can see that the FavList component is being imported within the script section as follows.

import FavList from '../components/FavList';

To understand how the import statement works, let's look at the following diagram.

Relationship between Import Statement and FavList Component

Figure 4-a: Relationship between Import Statement and FavList Component

The preceding diagram shows how the FavList relates to the FavList component referenced in the HTML markup (highlighted in red).

To get to the file path where the component is located, we need to come out of the pages folder, go one level up (..)—highlighted in yellow—and then go into the components folder (highlighted in b­lue).

The FavList component is contained within FavList (which is the FavList.vue file), highlighted in green. Notice how the .vue extension is not required when importing a component.

Next, we can see that we have included FavList within the components property of index.vue, as follows: components: { FavList }.

Following that, within the addToFavs method, we can access favlist array from the FavList component by using Vue.js template refs, as follows.

const favs = this.$refs.favlist;

Then, the favorites array (favlist) is accessed as favs.favorites.

FavList.vue

With index.vue refactored, let's open FavList.vue and copy and paste the following code.

Code Listing 4-e: FavList.vue

<template>

    <div>

        <div class="offcanvas offcanvas-start" data-bs-scroll="true"

            tabindex="-1"

            id="offcanvasWithBothOptions"

            aria-labelledby="offcanvasWithBothOptionsLabel">

            <div class="offcanvas-header">

                <h5 class="offcanvas-title"

                    id="offcanvasWithBothOptionsLabel">

                    Favorite Books

                </h5>

                <button type="button" class="btn-close text-reset"

                    data-bs-dismiss="offcanvas"

                    aria-label="Close">

                </button>

            </div>

            <div class="offcanvas-body">

                <div v-for="(fav, index) in favorites"

                    :key="fav.uuid + '_' + index"

                    class="card-mb-3">

                    <div class="row">

                        <div class="col-md-4">

                            <img :src="fav.picUrl"

                                class="img-fluid rounded-start">

                        </div>

                        <div class="col-md-8">

                            <div class="card-body">

                                <h5 class="card-title">

                                    {{ fav.name }}

                                </h5>

                                <p class="card-title">

                                    {{ fav.author }}

                                </p>

                                <div class="d-grid">

                                    <button @click="delFav(fav)"

                                      class="btn btn-outline-secondary">

                                        Remove from list

                                    </button>

                                </div>

                            </div>

                        </div>

                    </div>

                </div>

            </div>

        </div>

    </div>

</template>

<script>

    export default {

        name: 'FavList',

        data() {

            return {

                favorites: []

            }

        },

        methods: {

            delFav(fav) {

                const favs = this.favorites;

                const idx = favs.findIndex(

                                 item => item.uuid === fav.uuid);

                if (idx >= 0) {

                    favs.splice(idx, 1);

                }

            }

        }

    }

</script>

Within the template section, we can see that most of the HTML code is used for rendering the offcanvas Bootstrap component. There are two sections: the header (offcanvas-header) and body (offcanvas-body).

The header section contains the title used by the offcanvas component and a button to close it. On the other hand, the body section includes a picture of the book (fav.picUrl), the book's title (fav.name), the book's author (fav.author), and a button (button @click="delFav(fav)) to remove the book from the favorites list (favorites).

The body section repeats itself for all the books that have been added to the favorites list (favorites)—by executing this instruction: v-for="(fav, index) in favorites".

Regarding the script part of the preceding code, we can see that the data method returns the favorites array—previously declared within index.vue.

The delFav method, which receives the current or selected favorite book as a parameter (fav), removes that book from the list of favorite books by executing favs.splice(idx, 1), which means remove 1 item at position idx.

To retrieve the current or selected favorite book, we use the favs.findIndex method. The chosen book is determined by comparing the value of uuid the current item to the value of the book (fav) passed as a parameter to the delFav method.

Executing the app

Excellent—we've got a working app at this stage. It's a small application and doesn't do that much, but it does achieve the goal that we set out to accomplish: to display a list of books and add and remove favorites from that list.

To see the application running, we can execute it. To do that, run the npm run dev command from the built-in terminal in VS Code.

Executing the App (Built-in Terminal—VS Code)

Figure 4-b: Executing the App (Built-in Terminal—VS Code)

If we open the browser and navigate to the application's URL, we should see the following.

Application Running (1)

Figure 4-c: Application Running (1)

If we click each of the Add to Favorites buttons (below each of the book images) from left to right, and then click the Favorites button, we should see the following list of favorite books.

Application Running (2)

Figure 4-d: Application Running (2)

Looking at the list of favorite books, we can see that the books have been added to the list in the same order as each of the Add to Favorites buttons were clicked.

If we click the Remove from list button for the middle book (Azure Virtual Desktop Succinctly), we should see the following.

Application Running (3)

Figure 4-e: Application Running (3)

As we can see, the Azure Virtual Desktop Succinctly book has been successfully removed from the list of favorite books.

Summary

Congrats on what you've achieved so far! We have created all the application's client front-end logic. The next step is to work on the server-side logic and persist the data—which is what the next chapter is all about.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.