What is the best way to apply a specific style based on the book ID or card ID when a click event occurs on a specific card in vue.js

My latest project involves creating a page that displays a variety of books, with the data being fetched from a backend API and presented as cards.

Each book card features two button sections: the first section includes "ADD TO BAG" and "WISHLIST" buttons (initially visible), while the second section contains a "ADDED TO BAG" button (initially hidden).

When a user clicks on the "ADD TO BAG" button on a specific card, it should hide both the "ADD TO BAG" and "WISHLIST" buttons and display the "ADDED TO BAG" button exclusively for that clicked card. I need assistance in applying styles only to the clicked card, while the remaining cards should remain unaffected.

This is how the default page looks before clicking on the "ADD TO BAG" button: https://i.sstatic.net/rwIqV.png

After clicking on the "ADD TO BAG" button, this is the output I currently get: https://i.sstatic.net/svVHf.png

DisplayBooks.vue

<template>
  <div class="carddisplay-section">
      <div v-for="book in books" :key="book.id" class="card book">
          <div class="image-section">
              <div class="image-container">
                  <img  v-bind:src="book.file" />
              </div>
          </div>
          <div class="title-section">
              {{book.name}}
          </div>
          <div class="author-section">
              by {{book.author}}
          </div>
          <div class="price-section">
              Rs. {{book.price}}<label class="default">(2000)</label>
              <button v-if="flag" class="btn-grp" type="submit" @click="handlesubmit();Togglebtn();">close</button>
          </div>
          <div class="buttons">
  <!-- This is my button section -->            
  <div class="button-groups">
                  
                  <button type="button"  @click="toggle(book.id);toggleClass(book.id);" v-bind:class="[storeBooks.indexOf(book.id) >-1 ? 'red' : 'blue']" class="AddBag">Add to Bag</button>
                  
                  <button  class="wishlist">wishlist</button>
              </div>
              <!-- v-if="state==false" -->
              <div v-bind:class="[!(storeBooks.indexOf(book.id)) >-1 ? 'blue':'red']" @click="toggle(book.id)" class="AddedBag">
                  <button class="big-btn">Added to Bag</button>
              </div>
          </div>
      </div>
  <!-- <Cart :cardId="clickedCard" v-if="false" /> -->
  </div>
  </template>
  
  <script>
  import service from '../service/User'
  export default {
      data() {
          return {
              isActive:true,
              storeBooks:[],
              result: 0,
              authorPrefix: 'by',
              pricePrefix: 'Rs.',
              defaultStrikePrice: '(2000)',
              buttonValue: 'close',
              flag: true,
              state: true,
              clickedCard: '',
              books: [{
                  id: 0,
                  file: 'https://images-na.ssl-images-amazon.com/images/I/41MdP5Tn0wL._SX258_BO1,204,203,200_.jpg',
                  name: 'Dont Make me think',
                  author: 'Sai',
                  price: '1500'
              }, ]
          }
      },
      methods: {
          toggleClass: function(event){
              this.isActive = !this.isActive;
              return event;
          },
          toggle (id) {
            this.clickedCard = id;
            const index = this.storeBooks.indexOf(id);
            if(index > -1) {this.storeBooks = this.storeBooks.splice(index,1)}
            else{
              this.storeBooks.add(id)
            }},
          flip() {
              this.state = !this.state;
          },
          Togglebtn() {
              this.flag = !this.flag;
          },
          handlesubmit() {
              service.userDisplayBooks().then(response => {
                  this.books.push(...response.data);     
              })
          },
      }
  }
  </script>
  
  <style lang="scss" scoped>
      @import "@/styles/DisplayBooks.scss";
  </style>

Following the update to the component code, here is the current output: https://i.sstatic.net/Goc5L.png However, my expected outcome can be seen here: [what I need is]3

DisplayBooks.scss

@import "colors";
.carddisplay-section {
    display: flex;
    align-items: flex-start;
    flex-wrap: wrap;
    align-content: space-around;
    gap: 10px;
}
.card:hover{
    box-shadow:0.6px 0.6px 0.6px 0.6px rgb(173, 206, 206);
}
.card {
    margin-top: 55px;
    margin-left: 110px;
    background:$pink;
    width: 235px;
    height: 315px;
    background: $pale_white 0% 0% no-repeat padding-box;
    border: 1px solid $border_clr;
    border-radius: 3px;
    opacity: 1;
}

.image-section {
    width: 233px;
    height: 172px;
    background: #F5F5F5 0% 0% no-repeat padding-box;
    border-radius: 2px 2px 0px 0px;
    opacity: 1;
}

img{
    margin-left: 67px;
    margin-top: 17px;
    width: 105px;
    height: 135px;
    opacity: 1;
    border:none;
}

.title-section {
    text-align: left;
    font: normal normal normal 14px/19px Roboto;
    letter-spacing: 0.2px;
    color: $light_black;
    opacity: 1;
    margin-left: 20px;
    margin-top: 3px;
    width: 130px;
    height: 19px;
    text-transform: capitalize;
}

.author-section {
    text-align: left;
    font: normal normal normal 13px/13px Roboto;
    letter-spacing: 0px;
    color: $light_grey;
    opacity: 1;
    width: 123px;
    height: 13px;
    margin-left: 20px;
    margin-top: 7px;
}

.price-section {
    text-align: left;
    font: normal normal bold 12px/16px Roboto;
    letter-spacing: 0px;
    color: $light_black;
    opacity: 1;
    margin-left: 20px;
    height: 16px;
    margin-top: 26px;
    display: flex;
    justify-content: flex-start;

}

label {
    text-decoration-line: line-through;
    font: normal normal normal 10px/13px Roboto;
    letter-spacing: 0px;
    color: $light_grey;
    opacity: 1;
    width: 36px;
    height: 13px;
    margin-top: 2.5px;
    margin-left: 1em;
}

button[type="submit"] {
    border: none;
    padding-left: 65px;
    background: none;
    font-size: 15;
}
.button-groups{
    display:flex;
    margin-top:8px;
}
.AddBag{
    background: #A03037 0% 0% no-repeat padding-box;
    border-radius: 2px;
    opacity: 1;
    width: 93px;
    height: 29px;
    margin-left:20px;
    color: #FFFFFF;
    text-transform: uppercase;
    opacity: 1;
    font-size: small;
}
.wishlist{
    margin-left:4px;
    color: #FFFFFF;
    text-transform: uppercase;
    opacity: 1;
    font-size: small;
    border: 1px solid #7c7a7a;
    border-radius: 2px;
    opacity: 1;
    color: #0A0102;
    width:93px;
}
.big-btn{
    width: 191px;
height: 29px;
margin-left:20px;
background: #3371B5 0% 0% no-repeat padding-box;
border-radius: 2px;
opacity: 1;
color:#FFFFFF;
}
.red{
    background: red;
}   
.blue{
    background: yellow;
    display:none;
}

Answer №1

To keep track of which card is added, you can create an array in the component's data function called `storeBooks`, like this: storeBooks=[]

toggle = (id) => {
 const index = this.storeBooks.indexOf(id);
 if(index > -1) {this.storeBooks = this.storeBooks.splice(index,1)}
 else{
  this.storeBooks.add(id)
 }}

In the book card section, you can use

isActive = storeBooks.indexOf(book.id) > -1
to set a class accordingly.

Here's an example:

<template>
  <div class="carddisplay-section">
      <div v-for="book in books" :key="book.id" class="card book">
          <div class="image-section">
              <div class="image-container">
                  <img  v-bind:src="book.file" />
              </div>
          </div>
          <div class="title-section">
              {{book.name}}
          </div>
          <div class="author-section">
              by {{book.author}}
          </div>
          <div class="price-section">
              Rs. {{book.price}}<label class="default">(2000)</label>
              <button v-if="flag" class="btn-grp" type="submit" @click="handlesubmit();Togglebtn();">close</button>
          </div>
          <div class="buttons">
  <!-- This is my button section -->            
  <div class="button-groups">
                  
                  <button type="button"  @click="toggle(book.id);toggleClass(book.id);" v-bind:class="[storeBooks.indexOf(book.id) >-1 ? 'red' : 'blue']" class="AddBag">Add to Bag</button>
                  
                  <button  class="wishlist">wishlist</button>
              </div>
              <!-- v-if="state==false" -->
              <div v-bind:class="[storeBooks.indexOf(book.id) >-1 ? 'blue':'red']" @click="toggle(book.id)" class="AddedBag">
                  <button class="big-btn">Added to Bag</button>
              </div>
          </div>
      </div>
  <!-- <Cart :cardId="clickedCard" v-if="false" /> -->
  </div>
  </template>
  
  <script>
  import service from '../service/User'
  export default {
      data() {
          return {
              isActive:true,
              storeBooks:[],
              result: 0,
              authorPrefix: 'by',
              pricePrefix: 'Rs.',
              defaultStrikePrice: '(2000)',
              buttonValue: 'close',
              flag: true,
              state: true,
              clickedCard: '',
              books: [{
                  id: 0,
                  file: 'https://images-na.ssl-images-amazon.com/images/I/41MdP5Tn0wL._SX258_BO1,204,203,200_.jpg',
                  name: 'Dont Make me think',
                  author: 'Sai',
                  price: '1500'
              }, ]
          }
      },
      methods: {
          toggleClass: function(event){
              this.isActive = !this.isActive;
              return event;
          },
          toggle (id) {
            this.clickedCard = id;
            const index = this.storeBooks.indexOf(id);
            if(index > -1) {this.storeBooks = this.storeBooks.splice(index,1)}
            else{
              this.storeBooks.add(id)
            }},
          flip() {
              this.state = !this.state;
          },
          Togglebtn() {
              this.flag = !this.flag;
          },
          handlesubmit() {
              service.userDisplayBooks().then(response => {
                  this.books.push(...response.data);     
              })
          },
      }
  }
  </script>
  
  <style lang="scss" scoped>
      @import "@/styles/DisplayBooks.scss";
  </style>

Answer №2

If you're currently using just one boolean to check if a book has been added to the cart, it may be affecting all your other books when triggered. To avoid this issue, consider adding an extra key to your books data to track individual book statuses. You can achieve this by utilizing the spread operator and map function on arrays:

data  = data.map(item => ({...item, isAdded: false}))

This snippet of code will add an "isAdded" key with a value of false to each book object in your data. You can then use this new key in your HTML code with v-show to control which buttons are displayed. Here's an example:

<div class="button-groups" v-show="!book.isAdded">
  <button type="button" @click="book.isAdded=!book.isAdded;" class="AddBag red"&bt;Add to Bag</button>
  <button  class="wishlist">wishlist</button>
</div>

<div @click="book.isAdded=!book.isAdded" class="AddedBag blue" v-show="book.isAdded">
  <button class="big-btn">Added to Bag</button>
</div>

By using the "isAdded" variable in this way, you have better control over displaying the correct buttons. It's also recommended to choose a clearer name for the variable to improve understanding.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Choose the AuthGuard category in real-time

My application intends to employ two distinct authentication strategies - one for users accessing via a browser and another for the public API. A specific header will be set for browser users, allowing my app to determine the appropriate auth strategy base ...

Utilizing CSS variables and HSLA, create a distinctive linear gradient styling

I am encountering an issue with a CSS variable: :root{ --red: hsl(0, 100%, 74%); } Despite defining the variable, it does not work in the following code snippet: .page-wrapper{ background-image: linear-gradient(hsla(var(--red),.6), hsla(var(--red),.6 ...

Currently, my main focus is on finding a solution to adjusting the background color of the div element named *topnav* to properly match the width of the

I'm having trouble adjusting the width of the topnav background color to fill the entire page without any gaps. I hope that makes sense. Let me know if you need more information. Page displaying gap: Here is the HTML and CSS code: body {background ...

What could be causing my grid to malfunction once the media query is triggered?

I am in the process of creating a straightforward navigation bar. My goal is to have the <nav> content, which includes the navigation links, positioned inside the header along with the logo (a random image from unsplash.it). To achieve this layout, I ...

Error in Node: JSON parse failure due to invalid token "'<'" and ""<!DOCTYPE ""

Every time I attempt to run node commands or create a new Angular project, I encounter the following error. Node version 20.11.0 NPM version 10.2.4 https://i.sstatic.net/Dg6BU.png https://i.sstatic.net/ZwN1Q.png ...

The angularJS ternary expression is not considered valid

{{var.a != "N/A" ? "<a ng-href='myapp://find?name="+var.a+"'>"+'var.a'+"</a>" :var.a}} The ternary operator I have used in this Angularjs format does not seem to be working correctly. In the view, the ternary result is not ...

Can't decide between ngOnInit() and ngAfterContentInit()?

As a newcomer to Angular, I sometimes get confused about the ngOnInit() and ngAfterContentInit() lifecycle hooks. According to the official documentation: ngOnInit() : This hook is used to initialize the directive/component after Angular has displayed the ...

Discovering the droppable container that a draggable element is positioned within

Currently, I am utilizing jQuery UI for drag and drop functionality. My main goal is to determine which droppable area a draggable element has been placed in. Can anyone offer assistance? Below is the code I am working with: $(".draggable").draggable({ ...

The error encountered is related to the MongooseServerSelectionError that occurs in

I have been working on setting up my first mongo-db application to connect to the server. However, I am encountering a specific error during this process: const express = require('express'); const mongoose = require('mongoose'); const ...

Setting a custom background image in a Bootstrap modal

When users visit my website, I want to enhance their experience by using a background image in my bootstrap modal. Despite several attempts, I have been unable to successfully set a background image in the modal. Can anyone provide guidance on this matter? ...

Display or conceal a div element individually

My task involves modifying this code, which is the original version of a FAQ with three answers that can be toggled to show or hide on click. I need to revise it so that only one answer is displayed at a time and the others close. I received advice to us ...

We encountered a surprise issue: "/Users/username/package.json: Unexpected character \ in JSON at index 1" – this is not a duplicate question

While running the yarn command in various projects, I encountered the same error consistently. Error message: "An unexpected error occurred: "/Users/name/package.json: Unexpected token \ in JSON at position 1". Trace: SyntaxError: /Users/name/pack ...

Unable to change the font-size in Bootstrap 5 is not possible

I've been exploring Bootstrap 5 and encountered an issue while trying to change the font-size of the navbar-brand element. Despite my efforts, the font-size remained unchanged. Additionally, attempts to add padding to the navbar-brand did not result i ...

The onChange functionality of the Formik Field API is not compatible with a child component

I am currently facing an issue with validating a material-ui-dropzone component within the Formik Field API as a child component. When I try to upload a file, I encounter the following error: TypeError: can't access property "type", target is undefine ...

Learn the best way to efficiently transfer multiple checkbox selections in a single object using AJAX

In my form, I have 4 checkboxes with unique IDs like filter_AFFILIATION_1, filter_AFFILIATION_2, and so on up to 4. My goal is to dynamically send the values of checked checkboxes to the server using an ajax call. Below is the snippet of my code: $(&a ...

BOOTSTRAP Issue: The sticky footer in the template is not staying sticky

I am at the point where I am tempted to open my window and throw my computer out. I followed a tutorial on Bootstrap for the footer template. Everything looks good, but when my text extends beyond one page, the footer remains stuck in place and does not m ...

Compiling Directives in AngularJS for Faster Page Loading

I'm currently experiencing performance issues in my Angular application. The root cause seems to be the excessive use of a directive on a single page. Unfortunately, I don't have the time to break down this page into multiple sections. I am seek ...

I'm looking to configure @types for a third-party React JavaScript module in order to use it with TypeScript and bundle it with webpack. How can I accomplish this?

Imagine you have a third-party npm package called @foo that is all Javascript and has a module named bar. Within your TypeScript .tsx file, you want to use the React component @foo/bar/X. However, when you attempt to import X from '@foo/bar/X', y ...

Creating a customized SelectField component for Material-UI v1.0.0-alpha.21 with a fix for the Menu anchorEl problem

Currently, Material-UI v1.0.0 does not have a selectField implemented yet so I am attempting to create my own version using TextField, Menu, and MenuItem Components. Below is the code for my custom selectField: export default class SelectField extends Rea ...

Get the data in string format and save it as a CSV file

I've coded a script that transforms a JSON object into comma-separated values using the ConvertToCSV function. Now I'm wondering how to save the variable csvData as a downloadable CSV file? The code is already wrapped inside a function triggered ...