The never-ending scroll feature in Vue.js

For the component of cards in my project, I am trying to implement infinite scrolling with 3 cards per row. Upon reaching the end of the page, I intend to make an API call for the next page and display the subsequent set of cards. Below is my implementation code for infinite scrolling, which seems to be malfunctioning:

<template>
  <div
    class="container"
    ref="scrollContainer"
    @dragover.prevent="handleDragOverContainer"
    @drop.prevent="handleDrop"
  >
    <div class="card-container">
      <div class="card">
        <AddCourseCard />
      </div>
      <div
        class="card"
        v-for="(element, index) in products"
        :key="index"
        :draggable="true"
        @dragstart="handleDragStart(index)"
        @dragover.prevent="handleDragOverCard(index)"
        @dragend="handleDragEnd"
      >
        <CoursesCard
          :title="element.title"
          :text="element.text"
          :imageSrc="element.imageSrc"
          :productId="element.id"
        />
      </div>
    </div>
  </div>
</template>

<script>
import AddCourseCard from './AddCourseCard.vue'
import CoursesCard from './CourseCard.vue'
import { OfferService } from '../../service/index'
import { GroupState } from '../../composition/groups'
import Draggable from 'vuedraggable'

export default {
  name: 'Learning',
  components: {
    CoursesCard,
    AddCourseCard,
    draggable: Draggable,
  },
  data() {
    return {
      products: [],
      pageNo: 1, // Initialize pageNo
      limit: 10, // Set your desired limit per page
      loading: false,
      isGroupAdmin: GroupState.state.isGroupAdmin,
      dragData: null,
      draggedIndex: null,
      dropTargetIndex: null,
      reachedEnd: false,
      // Flag to prevent multiple requests while loading
      dragOptions: {
        // Specify any options for Vue.Draggable here if needed
        group: 'cards', // Add a group to allow cards to be dragged between different lists
        animation: 200, // Animation duration when items are reordered
      },
      dummyData: [
        {
          title: 'Card 1',
          text: 'Some quick example text for Card 1',
          imageSrc: 'https://picsum.photos/1600/1176/?image=25',
        },
        {
          title: 'Card 2',
          text: 'Some quick example text for Card 2',
          imageSrc: 'https://picsum.photos/1600/1176/?image=26',
        },
        {
          title: 'Card 3',
          text: 'Some quick example text for Card 3',
          imageSrc: 'https://picsum.photos/1600/1176/?image=27',
        },
        {
          title: 'Card 4',
          text: 'Some quick example text for Card 1',
          imageSrc: 'https://picsum.photos/1600/1176/?image=25',
        },
        {
          title: 'Card 5',
          text: 'Some quick example text for Card 2',
          imageSrc: 'https://picsum.photos/1600/1176/?image=26',
        },
        {
          title: 'Card 6',
          text: 'Some quick example text for Card 3',
          imageSrc: 'https://picsum.photos/1600/1176/?image=27',
        },
        // Add more dummy data items as needed
      ],
    }
  },
  mounted() {
    // Call the initial API request when the component is mounted

    this.fetchAllProducts()
    const container = this.$refs.scrollContainer
    if (container) {
      container.addEventListener('scroll', this.handleScroll)
    }
  },
  methods: {
    handleScroll() {
      const container = this.$refs.scrollContainer
      if (!container) return

      // Calculate when to trigger fetching more data based on scroll position
      const scrollPosition = container.scrollTop + container.clientHeight
      const maxScroll = container.scrollHeight

      if (
        scrollPosition >= maxScroll - 100 &&
        !this.loading &&
        !this.reachedEnd
      ) {
        // You can adjust the threshold (100 in this example) to your needs
        this.fetchNextPage()
      }
    },
    async fetchNextPage() {
      // Your existing code to fetch the next page of data
      // ...

      if (this.pagesEnd) {
        this.reachedEnd = true // Set the flag to indicate the end of data
      }
    },
    async fetchAllProducts() {
      if (!this.isGroupAdmin) {
        this.fetchDataFromAPIForGroup()
      } else {
        this.fetchDataFromAPI()
      }
    },
    async fetchDataFromAPI() {
      if (this.loading) return // Prevent multiple requests while loading

      this.loading = true
      try {
        const response = await OfferService.getOfferProductForUser({
          pageNo: this.pageNo,
          limit: this.limit,
        })

        // Append the new data to the existing products array
        this.products = [...this.products, ...response]

        // Increment the pageNo for the next request
        this.pageNo++
      } catch (error) {
        console.error('Error fetching data from API:', error)
      } finally {
        this.loading = false
      }
    },
    async fetchDataFromAPIForGroup() {
      if (this.loading) return // Prevent multiple requests while loading

      this.loading = true
      try {
        const response = await OfferService.getOfferProductForGroup({
          pageNo: this.pageNo,
          limit: this.limit,
        })

        // Append the new data to the existing products array
        this.products = [...this.products, ...response]

        // Increment the pageNo for the next request
        this.pageNo++
      } catch (error) {
        console.error('Error fetching data from API:', error)
      } finally {
        this.loading = false
      }
    },

    handleDragStart(index) {
      this.draggedIndex = index
    },
    handleDragOverCard(index) {
      this.dropTargetIndex = index
    },
    handleDragOverContainer(event) {
      // Prevent the default behavior, which is to not allow dropping
      event.preventDefault()
    },
    handleDrop() {
      if (this.draggedIndex !== null && this.dropTargetIndex !== null) {
        // Splice the dragged card out of the dummyData array
        const draggedCard = this.products.splice(this.draggedIndex, 1)[0]

        // Insert the dragged card at the drop target index
        this.products.splice(this.dropTargetIndex, 0, draggedCard)

        // Reset the dragged and drop target indices
        this.draggedIndex = null
        this.dropTargetIndex = null
      }
    },

    // Handle drag over
    handleDragOver(index) {
      if (!this.dragData) return

      // Prevent the default behavior, which is to not allow dropping
      event.preventDefault()

      // Swap the positions of the dragged item and the drop target
      const temp = this.dummyData[index]
      this.dummyData[index] = this.dragData
      this.dragData = temp
      console.log(this.dummyData)
    },

    // Handle drag end
    handleDragEnd() {
      this.dragData = null
    },
  },
}
</script>

<style scoped>
.container {
  display: flex;
  justify-content: center;
  /* Added to enable scrolling */
  overflow-y: auto;
}

.card-container {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  max-width: 1216px;
  margin: 0 auto;
}

.card {
  flex: 0 0 calc(33.33% - 10px);
  max-width: calc(33.33% - 10px);
  margin-bottom: 20px;
  box-sizing: border-box;
}

@media (max-width: 768px) {
  .card {
    flex: 0 0 calc(50% - 10px);
    max-width: calc(50% - 10px);
  }
}

@media (max-width: 480px) {
  .card {
    flex: 0 0 100%;
    max-width: 100%;
  }
}
</style>

I have included the styles as well, just in case there might be a CSS-related issue causing a blockage. Please assist me in understanding the problem here as I have been stuck trying to resolve it for hours.

Answer №1

Ensure that the scrollContainer div has a specified height. The overflow-y: auto; style will only add a scrollbar if the container has either a maximum or fixed height.

<div class="container" ref="scrollContainer" style="max-height: 70vh;" @dragover.prevent="handleDragOverContainer" @drop.prevent="handleDrop">

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

Modify the Div background color by accessing the HEX value saved in a JSON file

(I made a change to my code and removed the br tag from info2) Currently, I have successfully implemented a feature using jQuery that reads color names and hex values from a JSON file, populates them in a drop-down select menu (which is working fine). Now ...

Having difficulties with vue installation

Whenever I take a break from using certain packages, I typically opt for fresh re-installs. However, when I recently attempted to do a fresh install of Vue.js and vue-cli by running vue init on a new project, I encountered an error stating that Vue was not ...

Is it correct to implement an interface with a constructor in TypeScript using this method?

I am completely new to TypeScript (and JavaScript for the most part). I recently came across the article discussing the differences between the static and instance sides of classes in the TypeScript handbook. It suggested separating the constructor into an ...

Is it possible to generate a form dynamically in a Vue.js HTML file based on JSON data? Check out the provided f

Currently, I am diving into the world of vue.js and experimenting with building a dynamically created form. To explain further, I have JSON files that define what input types should be included in the form; now I am figuring out how to implement this usin ...

Using Highcharts to dynamically color a map based on data values in a React TypeScript project

I'm attempting to generate a map where each country is filled with colors based on its specific data, similar to the example shown in this map. I am looking for a functionality akin to the use of the formatter function within the tooltip. I have expe ...

Steps for including variables assigned in a PHP file into a PHTML file

I am having trouble loading all the content from a phtml file into a php file Everything is functioning correctly except that the variables in the phtml file are not being loaded with their values Below is my php file <?php $firstname = $_POST[' ...

Separate compound words without hyphens at the boundaries of each word

I am currently incorporating the use of Bootstrap for my project. Let's assume that I have text displayed in this way: "Stackoverflow" Is there a method to automatically ensure that it breaks like below if the text exceeds the container: "Stack ...

Achieve the effect of making the Bootstrap JS Collapse text bold after it has been

Is there a way to make Bootstrap JS Collapse text Bold after it has been clicked on? <tr data-toggle="collapse" data-target="#demo8" class="accordion-toggle"> <td> <div class="fa ...

A step-by-step guide to activating multi-selection in the Primary SideBar of Visual Studio Code using your custom extension

Currently, I'm in the process of developing an extension for Visual Studio Code where I've added a new activityBar containing treeViews that showcase information pulled from a JSON file. My goal is to enable users to multi-select certain displaye ...

The removal of a JQuery element can result in causing the webpage to become unresponsive and lead to

When attempting to create a loop in JQuery to remove elements from my HTML, I encountered an issue where the function caused my browser to hang and become unresponsive. Here is the JQuery code I used: function removeElement(){ var i =0; ...

Console.log is displaying array as [object Object] when utilizing Typescript

When working with an object in typescript called "obj," I encountered a strange behavior. Initially, when I ran the console.log(obj); command, the output in the terminal console was displayed as [object Object]. However, after wrapping it in JSON.stringify ...

Comparison of XPath operators //* versus //element versus //

My mind is a bit muddled when it comes to finding XPath: I'm not sure when to use //* at the beginning and when just // will suffice. For instance, I recently encountered this issue while trying to understand it on . On their homepage, there is a sea ...

Storing data efficiently with Angular 2's local storage service

I am attempting to create a ToDoList using localstorage within a service. add.component.ts export class AddComponent implements OnInit { item: Item[]; constructor( private router: Router, private itemService: ItemService) { } ...

Innovative way to design a menu using HTML, CSS, and JavaScript that dynamically resizes to a specific width

I'm currently working on a website and I'm trying to create a menu with specific width of 700px. However, I'm unsure whether to tackle this using CSS, JavaScript, or a combination of both. Which approach would be the most straightforward for ...

Having Multiple File Inputs [type=file] in one webpage

Is it possible to have two separate inputs for uploading images, each setting a different background image in a div? The second file input is: <input type='file' id='getval' name="logo" /> I want this file to be set as the back ...

Stylish CSS typography options for both Mac and Windows users

When browsing various websites, I've noticed that many of them showcase beautiful fonts without using images. Examples include The Firebug site and Happy Cog. It seems like web designers use CSS tricks and JavaScript to achieve this effect. I'm ...

Differences between Typescript static methods and functions defined outside of classesWhen comparing Types

What distinguishes a static class method from a function defined outside a class in TypeScript, especially when visibility is not a concern? While there are differences in visibility from other classes and files, what factors should be considered when dec ...

Ensure that the div stays in the same position regardless of the screen resolution

On my webpage, I have a page header with a select menu that needs to be properly positioned on top of the background image. How can I ensure it stays in place when the screen resolution changes? Here is how it looks on a larger resolution: <div c ...

Vue 3's click event handler does not recognize $options.x as a valid function

I'm encountering a perplexing issue. I am currently in the process of creating a Wordpress plugin using Vue, but unfortunately, I am unable to establish functionality for the @click event. <script> export default { name: "App", me ...

Troubleshooting a Navigation Styling Issue in HTML and CSS

I'm struggling to position my drop-down menus correctly in my WordPress theme navigation. They are appearing far away from the nav bar and end up near the top left corner of the screen when I hover over a ul li >. Here is the CSS code I am using: ...