Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

boyjarv's avatar

paginating from my response

hi, so I have a data response as follows::

data: {offset: 0, limit: 20, total: 1559, count: 20, results....

How can I use this to paginate to the next and previous page?

Thanks

0 likes
23 replies
tykus's avatar

How have you come to have this response; what Paginator are you using?

jlrdw's avatar

You can use a length aware paginator, just suggestion. It's covered in the laravel API.

1 like
boyjarv's avatar

so I don't have access to the backend. I just have the results in the api response

tykus's avatar

What should a URL for the next page look like; is there a page query parameter, or do you provide an offset and count???

1 like
boyjarv's avatar

I do have the option to pass limit and offset in:

?limit=100&offset=10
jlrdw's avatar

You can make next prev links using the provided query string.

Edit: sometimes you have to keep track of the current page or offset using a hidden field. Or however you need. Just make the links.

2 likes
jlrdw's avatar

They are just a href links.

I am on mobile, I will boot my desktop.

2 likes
jlrdw's avatar

@boyjarv see this video: https://laracasts.com/series/javascript-techniques-for-server-side-developers/episodes/1 Server fetched partials and adapt to your case.

Basically a next

        $("#nextp").click(function (event)
        {
            var variable = $('#counter').html();
            var newvar = parseInt(variable) + 1;
            var maxpage = parseInt($('#mp').html());
            if (newvar > maxpage) {
                newvar = maxpage;
            }
            $('#counter').text(newvar);
            var url = '<?php echo DIR . "account/index?page="; ?>' + newvar;

            var element = document.getElementById('div1');
            fload(url, element);
        });
<a href="javascript:void(0);" id="nextp">next</a>  // a link

And the fetch:

    function fload(url, element)
    {
        fetch(url)
                .then(function (response) {
                    return response.text();
                })
                .then(function (body) {
                    element.innerHTML = body;
                });
    }

I use a partial page load, but you should be able to adapt the video and example to your use case. I use some hidden fields to track max page and current page, in example I just used counter.

Example and suggestions only. Also a suggestion, take more lessons, many free javascript lessons on laracasts.

2 likes
tykus's avatar

You could make a class to interact with the response data:

class Paginator {
  constructor (response) {
    this.response = response;
    this.path = window.location.pathname;
    this.query = new URLSearchParams(window.location.search);
  }
  nextPage () {
    let offset = this.response.offset + this.response.limit;
    this.query.set('offset', (offset < this.response.total) ? offset : this.response.offset);
    return `${this.path}?${this.query}`;
  }
  prevPage() {
    // similar implementation
  }
}
let p = new Paginator(response);

// p.nextPage()

You will basically pass the response into the Paginator class, and nextPage, prevPage urls - you could go further if you need more from your Paginator

1 like
boyjarv's avatar

hmm, shouldn't pagination be really easy these days?

tykus's avatar

There may be something on NPM 🤷‍♂️ - generally a well-designed API response will include previous and next links

3 likes
jlrdw's avatar

You need to learn how to write a paginator, it is easy to write one. Just suggestion.

But for one in custom javascript, the one in laravel won't work, I just create custom links.

3 likes
boyjarv's avatar

Thank you all for your help!

1 like
tykus's avatar

It is really not difficult to implement something like this yourself.

2 likes
boyjarv's avatar

Here is the data that is returned in from the api call, just a bit stuck on how to implement this:

"data": {
    "offset": 0,
    "limit": 20,
    "total": 30920,
    "count": 20,
    "results": [{array of objects}}]
  }
tykus's avatar

Like I mentioned previously, you can make a class to handle this data; share your Vue component script and I'll show you.

boyjarv's avatar

Vue component script:

import { mapState } from 'vuex'
    export default {
        name: 'Character',
        data() {
            return {
                charImageUrl: '',
                charImageSize: 'detail.jpg',
                charStores: [],
                charSeries: [],
                charEvents: [],
                personalData: {
                    name: 'BONZO',
                    imgUrl: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTn9gW4RiQbFYtV8YUcZGd-iocJ98Nq9oZ38A&usqp=CAU'
                }
            }
        },
        computed: {
            ...mapState({
                character: state => state.character,
                preUrl: state => state.charImageUrl,
                stories: state => state.charStories,
                series: state => state.charSeries,
                events: state => state.charEvents,
                pd: state => state.personalData
            })
        },
        mounted() {
            console.log('pd:', this.pd)
            
            this.$store.dispatch('getCharacter', this.$route.params.id, this.personalData)
            this.getImage()
            this.setPersonalData()
        },
        methods: {
            getImage() {
                this.charImageUrl = `${this.preUrl}/${this.charImageSize}`
            },
            setPersonalData() {
                this.$store.commit("setPersonal", this.personalData)
            }
        }
    }

VUX:

import { createStore } from 'vuex'
import axios from 'axios'
import { public_key } from '../marvel'

export default createStore({
  state: {
    characters: [],
    character: [],
    charImageUrl: '',
    charStories: [],
    charSeries: [],
    charEvents: [],
    personalData: {
      name: '',
      imageUrl: ''
    }
  },
  mutations: {
    getCharacters(state) {
      state.characters = []
      axios.get(`http://gateway.marvel.com/v1/public/characters?apikey=${public_key}`)
        .then((res) => {
          console.log('res: ', res)
            res.data.data.results.forEach((item) => {
                state.characters.push(item)
            })
        })
        .catch((err) => {
            console.log(err)
        })
    },
    getCharacter(state, id){
      state.character = []
      state.charStories = []
      state.charSeries = []
      state.charEvents = []
      axios.get(`http://gateway.marvel.com/v1/public/characters/${id}?apikey=${public_key}`)
      .then(res => {
          res.data.data.results.forEach((item) => {
              state.character.push(item)
              state.charImageUrl = `${item.thumbnail.path}/`
              item.stories.items.forEach((story) => {
                state.charStories.push(story)
              })
              item.series.items.forEach((programme) => {
                state.charSeries.push(programme)
              })
              item.events.items.forEach((event) => {
                state.charEvents.push(event)
              })
          })
      })
      .catch((err) => {
          console.log(err)
      })
    },
    setPersonal (state, personalData) {
      state.personalData = personalData
    }
  },
  actions: {
    getCharacters: context => {
      context.commit('getCharacters')
    },
    getCharacter(context, id) {
      context.commit('getCharacter', id)
    }
  },
  modules: {
  }
})
tykus's avatar

Where's the query with offset and limit query params???

boyjarv's avatar

I would add it into here:

axios.get(`http://gateway.marvel.com/v1/public/characters?apikey=${public_key}`)
tykus's avatar
// resources/js/Paginator.js
export default class Paginator {
  constructor(response, url = null) {
    this.response = response;
    this.url = new URL(url || window.location.href)
  }
  items () {
    return this.response.results;
  }
  hasMore () {
	return this.response.offset + this.response.limit < this.response.
  }
  nextPageUrl() {
    this.url.searchParams.set('offset', this.hasMore() ? this.response.offset + this.response.limit : this.response.offset);
    this.url.searchParams.set('limit', this.response.limit);
    return this.url.toString();
  }
  prevPageUrl() {
    this.url.searchParams.set('offset', Math.max(0, this.response.offset - this.response.limit));
    this.url.searchParams.set('limit', this.response.limit);
    return this.url.toString();
  }
}

Then pass the response of the successful axios request into a new Paginator instance, along with the URL so we can build the prev and next URLs

const Paginator = require('@/Paginator.js');
//...
    getCharacters(state) {
      state.characters = []
      let url = `http://gateway.marvel.com/v1/public/characters?apikey=${public_key}`
      axios.get(url)
        .then((res) => {
            let characters = new Paginator(res, url);
            state.characters.concat(...characters.items());

            // you can get the prevPage and nextPage URLs from the Paginator
            // depending on how you intend to use them, you could decide to
            // put this data in `state` along with the original URL above. 
        })

I should also say; hitting a third party API directly from your frontend is possibly not the best idea. Your API key is exposed; and you would probably burn through the hourly/daily limits quickly. In these situations, I would handle queries to the third party API in my Laravel app, exposing an internal endpoint which provides data to the Vue application. In this scenario, you are hiding your API key from malicious users, and you have the opportunity to cache the API responses, therefore giving you control over how often it hit.

1 like
boyjarv's avatar

Thank you for this Tykus, it's a bit much for what I need to do...

I am now getting: TypeError: Paginator is not a constructor at eval (index.js?4360:26)

Please or to participate in this conversation.