javascriptalgorithmmath

Javascript - How to calculate pagination range number for each page of dynamic dataset?


Im building an automatic pagination calculator that calculates offset and range according to user input. A very simple task but my algorithm is off because of the offset starting at 0 in the for loop. No matter how you put it, starting at 0 or 1, the numbers will always be off. How can I make this work?

For example entering 100 total items and 25 per page is off by 4 :

page : 1 | offset : 0 | range : 1 - 25 | total : 24
page : 2 | offset : 25 | range : 26 - 50 | total : 24
page : 3 | offset : 50 | range : 51 - 75 | total : 24
page : 4 | offset : 75 | range : 76 - 100 | total : 24

//event listener
var button = document.getElementById('submit');

//click
button.addEventListener('click',function(){
  
//div results  
var results = document.getElementById('results');
  
//clear div before appending
results.innerHTML= '';

//grab value
var total_items = document.getElementById('total_items').value;//331165139
var per_page =  document.getElementById('items_per_page').value;

//total pages
var total_pages = Math.ceil(total_items/per_page);

//iterate over pages
for(var x = 0; x<total_pages; x++){

//get page
var page = x+1;

//offset
var offset = (per_page * page) - per_page;

//range from
var from = ((page - 1) * per_page) + 1 ;

//range to
var to = per_page * page
var range = from + ' - ' + to;

//creating divs
var newDiv = document.createElement('div');
newDiv.innerHTML =  'page : ' + page + ' | offset : ' + offset + ' | range : ' + range + ' | total : ' + (to-from);
results.appendChild(newDiv);
 
}

})
<h3>Pagination Calculator</h3>
<hr>
<p>Calculates pagination automatically in the console</p>
<p>*Pop open the console</p>
<hr>
<label>total items </label>
<input id="total_items">
<label>limit per page</label>
<input id="items_per_page">
<button id="submit">Submit</button>
<hr>
<div id="results"></div>


Solution

  • The simplest answer as to what's wrong with your code is that you have an off-by-one error. These are very common.

    When you want to count the integers between a and b, inclusive, the count is not b - a. It's b - a + 1. It's easy to see with an example. If you want to count the numbers between 5 and 8, we have 5, 6, 7, and 8, for a total of 4 numbers. But 8 - 5 is 3. We need to add one to account for the second included endpoint.

    But I would look at the problems a bit differently.

    Without trying to replicate your UI, I might write a function like this:

    const paginate = (items, per) =>
      Array .from ({length: Math .ceil (items / per)}, (_, i) => ({
        page: i + 1,
        offset: i * per,
        range: [i * per + 1, Math .min ((i + 1) * per, items)],
        total: Math .min ((i + 1) * per, items) - (i * per)
      }))
    
    console .log (paginate (99, 8))
    .as-console-wrapper {max-height: 100% !important; top: 0}

    This gets all pages at once. An alternative would be to dynamically create the page range when it's requested, using a page number. (It could alternatively be done with an offset; there are pros and cons to each.)

    That might look like this (Update: fixed in response to comment from Robbiegod):

    Fixed, 2024-08-23

    const nextPage = (
      items = [], per = 10, page = 1, top = Math .min (page * per, items)
    ) => ({
      page,
      range: [(page - 1) * per + 1, top],
      total:  Math .max (0, top - ((page - 1) * per)),
      done: top >= items
    })
                      
    console .log (nextPage (99, 8, 1))  // `done` is false
    console .log (nextPage (99, 8, 2))  //  ...
    console .log (nextPage (99, 8, 3))  //  ...
    console .log ('....')               // `done` stays false
    console .log (nextPage (99, 8, 11)) //  ...
    console .log (nextPage (99, 8, 12)) //  ...
    console .log (nextPage (99, 8, 13)) // `done` is now true
    .as-console-wrapper {max-height: 100% !important; top: 0}

    Old, broken version

    const nextPage = (
      items = [], per = 10, page = 1, top = Math .min ((page + 1) * per, items)
    ) => ({
      page,
      range: [page * per + 1, top],
      total:  Math .max (0, top - (page * per)),
      done: top >= items
    })
                      
    console .log (nextPage (99, 8, 1))  // `done` is false
    console .log (nextPage (99, 8, 2))  //  ...
    console .log (nextPage (99, 8, 3))  //  ...
    console .log ('....')               // `done` stays false
    console .log (nextPage (99, 8, 11)) //  ...
    console .log (nextPage (99, 8, 12)) // `done` is now true
    .as-console-wrapper {max-height: 100% !important; top: 0}

    Also, do note the comment that said

    My friend, do realize inputElement.value returns a string!!!

    This happened to not hurt you because the only arithmetic you did with your input values was to multiply or subtract them. Had you tried to add, you might have gotten some surprising answers because of string concatenation.