Home > other >  Js filter a multiple page list
Js filter a multiple page list

Time:11-04

I have a working filtered list using JS, but the amount of list elements is growing. So I need to divide the list in pages, but for the filter to keep working for the whole list, even if the list element is not showing because it is in another page.

I've seen how to make a "previous" and "next" buttons for list, but it breaks down the filter js, because it consists basically of placing the contents on different divs, but the filter will only filter the first one.

This is my HTML now:

<section>
  <input type="text" id="barraBusqueda" onkeyup="buscar()" placeholder="Buscar artículos">
  <ul id="articulos">
    <li><a href="/es/blog/1.html">Article 1</a></li>
    <li><a href="/es/blog/2.html">Article 2</a></li>
    <li><a href="/es/blog/3.html">Article 3</a></li>
    <li><a href="/es/blog/4.html">Article 4</a></li>
    <li><a href="/es/blog/5.html">Article 5</a></li>
    <li><a href="/es/blog/6.html">Article 6</a></li>
    <li><a href="/es/blog/7.html">Article 7</a></li>
    <li><a href="/es/blog/8.html">Article 8</a></li>
    <li><a href="/es/blog/9.html">Article 9</a></li>
    <li><a href="/es/blog/10.html">Article 10</a></li>
  </ul>
</section>
    

And my JS:

function buscar() {
  // Declare variables
  var input, filter, ul, li, a, i, txtValue;
  input = document.getElementById("barraBusqueda");
  filter = input.value.toUpperCase();
  ul = document.getElementById("articulos");
  li = ul.getElementsByTagName("li");

  // Loop through all list items, and hide those who don't match the search query
  for (i = 0; i < li.length; i  ) {
    a = li[i].getElementsByTagName("a")[0];
    txtValue = a.textContent || a.innerText;
    if (txtValue.toUpperCase().indexOf(filter) > -1) {
      li[i].style.display = "";
    } else {
      li[i].style.display = "none";
    }
  }
}

It works fine, but all the elements have to be showing, and I don't want an eternal scroll through dozens of list items.

Goal would be that the browser only displays 5 list items at a time, with a next and previous button to navigate through them, but that the filter considers all the list items, even those that are in other pages.

CodePudding user response:

I would recommend to separate the data from the representation. This makes it easier to combine filtering and paging.

As a first step I would create an array of objects describing the list of entries. On this array you can perform filtering. Secondly, I would write a function which renders the list entries. This way you don't have to hide HTML elements, you only render those that match your filter and/or page.

Please take a look at this (incomplete) working example:

// The maximum size of a single page
const pageSize = 5;

// All list entries (unfiltered)
const data = [
   { name: 'Article 1', url: '/es/blog/1.html' },
   { name: 'Article 2', url: '/es/blog/2.html' },
   { name: 'Article 3', url: '/es/blog/3.html' },
   { name: 'Article 4', url: '/es/blog/4.html' },
   { name: 'Article 5', url: '/es/blog/5.html' },
   { name: 'Article 6', url: '/es/blog/6.html' },
   { name: 'Article 7', url: '/es/blog/7.html' },
   { name: 'Article 8', url: '/es/blog/8.html' },
   { name: 'Article 9', url: '/es/blog/9.html' },
   { name: 'Article 10', url: '/es/blog/10.html' },
   { name: 'Article 11', url: '/es/blog/11.html' },
   { name: 'Article 12', url: '/es/blog/12.html' },
   { name: 'Article 13', url: '/es/blog/13.html' },
   { name: 'Article 14', url: '/es/blog/14.html' },
];

// The filtered data; initially equal to `data`; 
// modified by function filter()
let filteredData = data;

// The current page index
let currentPage = 0;

// Adds/removes the `disabled` attribute from the element by
// specified ID according to the `disabled` flag
function updateButtonState(id, disabled) {
  const button = document.getElementById(id);
  if (disabled) {
    button.setAttribute('disabled', 'true');
  }
  else {
    button.removeAttribute('disabled');
  }
}

// Updates the states of the Previous/Next buttons
function updateButtonStates() {
  updateButtonState('btn-prev', currentPage === 0);
  updateButtonState('btn-next', (currentPage   1) * pageSize >= filteredData.length);
}

// Filters the `data` array according to the given text
function filter(text) {
  filteredData = data.filter(entry => entry.name.includes(text));
  renderPage(currentPage);
}

// Renders a single page, using the `filteredData` array
function renderPage(pageNo) {
  currentPage = pageNo;

  const elList = document.getElementById('list');
  elList.innerHTML = '';

  const offset = pageNo * pageSize;
  for (let i = offset; i < offset   pageSize && i < filteredData.length; i  ) {
    const entry = filteredData[i];
    const li = elList.appendChild(document.createElement('li'));
    const anchor = li.appendChild(document.createElement('a'));
    anchor.innerHTML = entry.name;
    anchor.href = entry.url;
  }
  
  updateButtonStates();
}


// Render first page initially
renderPage(0);
<input type="text" onkeyup="filter(this.value)">

<ul id="list">
</ul>

<button id="btn-prev" onclick="renderPage(currentPage - 1)">Previous</button>
<button id="btn-next" onclick="renderPage(currentPage   1)">Next</button>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Test it by entering 1 into the search field.

  • Related