Pagination

Cards with Text Search and Pagination

Simple x-for example with local JSON data source

Using filter to filter the cards

Basic pagination on the data with page counts and totals

Using icons from Feather Icon pack

If this component helped you, why not

Buy Me A Coffee

Advertisment

Preview:

Code


The code for the starter component which you can drop into your existing project.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Alpine.js Example - Cards with Search and Pagination</title>
    <link
      href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css"
      rel="stylesheet"
    />
    <script
      src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js"
      defer
    ></script>
  </head>

  <body class="bg-grey-100 px-3 font-sans leading-normal tracking-normal">
    <div class="container pt-8 mx-auto" x-data="loadEmployees()">
      <input
        x-ref="searchField"
        x-model="search"
        x-on:click="viewPage(0)"
        x-on:keydown.window.prevent.slash=" viewPage(0), $refs.searchField.focus()"
        placeholder="Search for an employee..."
        type="search"
        class="block w-full bg-gray-200 focus:outline-none focus:bg-white focus:shadow text-gray-700 font-bold rounded-lg px-4 py-3"
      />
      <div class="mt-4 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
        <template x-for="item in filteredEmployees" :key="item">
          <div
            class="flex items-center shadow hover:bg-indigo-100 hover:shadow-lg hover:rounded transition duration-150 ease-in-out transform hover:scale-105 p-3"
          >
            <img
              class="w-10 h-10 rounded-full mr-4"
              :src="`${item.profile_image}`"
            />
            <div class="text-sm">
              <p
                class="text-gray-900 leading-none"
                x-text="item.employee_name + ' (' + item.employee_age + ')'"
              ></p>
              <p
                class="text-gray-600"
                x-text="'$'+item.employee_salary/100"
              ></p>
            </div>
          </div>
        </template>
      </div>

      <div
        class="w-full md:w-1/2 mx-auto py-6 flex justify-between items-center"
        x-show="pageCount() > 1"
      >
        <!--First Button-->
        <button
          x-on:click="viewPage(0)"
          :disabled="pageNumber==0"
          :class="{ 'disabled cursor-not-allowed text-gray-600' : pageNumber==0 }"
        >
          <svg
            class="h-8 w-8 text-indigo-600"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <polygon points="19 20 9 12 19 4 19 20"></polygon>
            <line x1="5" y1="19" x2="5" y2="5"></line>
          </svg>
        </button>

        <!--Previous Button-->
        <button
          x-on:click="prevPage"
          :disabled="pageNumber==0"
          :class="{ 'disabled cursor-not-allowed text-gray-600' : pageNumber==0 }"
        >
          <svg
            class="h-8 w-8 text-indigo-600"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <polyline points="15 18 9 12 15 6"></polyline>
          </svg>
        </button>

        <!-- Display page numbers -->
        <template x-for="(page,index) in pages()" :key="index">
          <button
            class="px-3 py-2 rounded"
            :class="{ 'bg-indigo-600 text-white font-bold' : index === pageNumber }"
            type="button"
            x-on:click="viewPage(index)"
          >
            <span x-text="index+1"></span>
          </button>
        </template>

        <!--Next Button-->
        <button
          x-on:click="nextPage"
          :disabled="pageNumber >= pageCount() -1"
          :class="{ 'disabled cursor-not-allowed text-gray-600' : pageNumber >= pageCount() -1 }"
        >
          <svg
            class="h-8 w-8 text-indigo-600"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <polyline points="9 18 15 12 9 6"></polyline>
          </svg>
        </button>

        <!--Last Button-->
        <button
          x-on:click="viewPage(Math.ceil(total/size)-1)"
          :disabled="pageNumber >= pageCount() -1"
          :class="{ 'disabled cursor-not-allowed text-gray-600' : pageNumber >= pageCount() -1 }"
        >
          <svg
            class="h-8 w-8 text-indigo-600"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <polygon points="5 4 15 12 5 20 5 4"></polygon>
            <line x1="19" y1="5" x2="19" y2="19"></line>
          </svg>
        </button>
      </div>

      <div>
        <div
          class="mt-6 flex flex-wrap justify-between items-center text-sm leading-5 text-gray-700"
        >
          <div
            class="w-full sm:w-auto text-center sm:text-left"
            x-show="pageCount() > 1"
          >
            Page <span x-text="pageNumber+1"> </span> of
            <span x-text="pageCount()"></span> | Showing
            <span x-text="startResults()"></span> to
            <span x-text="endResults()"></span>
          </div>

          <div
            class="w-full sm:w-auto text-center sm:text-right"
            x-show="total > 0"
          >
            Total <span class="font-bold" x-text="total"></span> results
          </div>

          <!--Message to display when no results-->
          <div
            class="mx-auto flex items-center font-bold text-red-500"
            x-show="total===0"
          >
            <svg
              class="h-8 w-8 text-red-500"
              viewBox="0 0 24 24"
              stroke-width="2"
              stroke="currentColor"
              fill="none"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <path stroke="none" d="M0 0h24v24H0z" />
              <circle cx="12" cy="12" r="9" />
              <line x1="9" y1="10" x2="9.01" y2="10" />
              <line x1="15" y1="10" x2="15.01" y2="10" />
              <path d="M9.5 16a10 10 0 0 1 6 -1.5" />
            </svg>

            <span class="ml-4"> No results!!</span>
          </div>
        </div>
      </div>
    </div>
    <script>
      var sourceData = [
        {
          id: "1",
          employee_name: "Tiger Nixon",
          employee_salary: "320800",
          employee_age: "61",
          profile_image: "https://randomuser.me/api/portraits/men/1.jpg",
        },
        {
          id: "2",
          employee_name: "Garrett Winters",
          employee_salary: "170750",
          employee_age: "63",
          profile_image: "https://randomuser.me/api/portraits/men/2.jpg",
        },
        {
          id: "3",
          employee_name: "Ashton Cox",
          employee_salary: "86000",
          employee_age: "66",
          profile_image: "https://randomuser.me/api/portraits/men/3.jpg",
        },
        {
          id: "4",
          employee_name: "Cedric Kelly",
          employee_salary: "433060",
          employee_age: "22",
          profile_image: "https://randomuser.me/api/portraits/men/4.jpg",
        },
        {
          id: "5",
          employee_name: "Airi Satou",
          employee_salary: "162700",
          employee_age: "33",
          profile_image: "https://randomuser.me/api/portraits/women/5.jpg",
        },
        {
          id: "6",
          employee_name: "Brielle Williamson",
          employee_salary: "372000",
          employee_age: "61",
          profile_image: "https://randomuser.me/api/portraits/women/6.jpg",
        },
        {
          id: "7",
          employee_name: "Herrod Chandler",
          employee_salary: "137500",
          employee_age: "59",
          profile_image: "https://randomuser.me/api/portraits/men/7.jpg",
        },
        {
          id: "8",
          employee_name: "Rhona Davidson",
          employee_salary: "327900",
          employee_age: "55",
          profile_image: "https://randomuser.me/api/portraits/women/8.jpg",
        },
        {
          id: "9",
          employee_name: "Colleen Hurst",
          employee_salary: "205500",
          employee_age: "39",
          profile_image: "https://randomuser.me/api/portraits/women/9.jpg",
        },
        {
          id: "10",
          employee_name: "Sonya Frost",
          employee_salary: "103600",
          employee_age: "23",
          profile_image: "https://randomuser.me/api/portraits/women/10.jpg",
        },
        {
          id: "11",
          employee_name: "Jena Gaines",
          employee_salary: "90560",
          employee_age: "30",
          profile_image: "https://randomuser.me/api/portraits/women/11.jpg",
        },
        {
          id: "12",
          employee_name: "Quinn Flynn",
          employee_salary: "342000",
          employee_age: "22",
          profile_image: "https://randomuser.me/api/portraits/men/12.jpg",
        },
        {
          id: "13",
          employee_name: "Charde Marshall",
          employee_salary: "470600",
          employee_age: "36",
          profile_image: "https://randomuser.me/api/portraits/women/12.jpg",
        },
        {
          id: "14",
          employee_name: "Haley Kennedy",
          employee_salary: "313500",
          employee_age: "43",
          profile_image: "https://randomuser.me/api/portraits/women/14.jpg",
        },
        {
          id: "15",
          employee_name: "Tatyana Fitzpatrick",
          employee_salary: "385750",
          employee_age: "19",
          profile_image: "https://randomuser.me/api/portraits/women/15.jpg",
        },
        {
          id: "16",
          employee_name: "Michael Silva",
          employee_salary: "198500",
          employee_age: "66",
          profile_image: "https://randomuser.me/api/portraits/men/16.jpg",
        },
        {
          id: "17",
          employee_name: "Paul Byrd",
          employee_salary: "725000",
          employee_age: "64",
          profile_image: "https://randomuser.me/api/portraits/men/17.jpg",
        },
        {
          id: "18",
          employee_name: "Gloria Little",
          employee_salary: "237500",
          employee_age: "59",
          profile_image: "https://randomuser.me/api/portraits/women/18.jpg",
        },
        {
          id: "19",
          employee_name: "Bradley Greer",
          employee_salary: "132000",
          employee_age: "41",
          profile_image: "https://randomuser.me/api/portraits/men/19.jpg",
        },
        {
          id: "20",
          employee_name: "Dai Rios",
          employee_salary: "217500",
          employee_age: "35",
          profile_image: "https://randomuser.me/api/portraits/men/20.jpg",
        },
        {
          id: "21",
          employee_name: "Jenette Caldwell",
          employee_salary: "345000",
          employee_age: "30",
          profile_image: "https://randomuser.me/api/portraits/women/21.jpg",
        },
        {
          id: "22",
          employee_name: "Yuri Berry",
          employee_salary: "675000",
          employee_age: "40",
          profile_image: "https://randomuser.me/api/portraits/men/22.jpg",
        },
        {
          id: "23",
          employee_name: "Caesar Vance",
          employee_salary: "106450",
          employee_age: "21",
          profile_image: "https://randomuser.me/api/portraits/men/23.jpg",
        },
        {
          id: "24",
          employee_name: "Doris Wilder",
          employee_salary: "85600",
          employee_age: "23",
          profile_image: "https://randomuser.me/api/portraits/women/24.jpg",
        },
      ];

      function loadEmployees() {
        return {
          search: "",
          pageNumber: 0,
          size: 10,
          total: "",
          myForData: sourceData,

          get filteredEmployees() {
            const start = this.pageNumber * this.size,
              end = start + this.size;

            if (this.search === "") {
              this.total = this.myForData.length;
              return this.myForData.slice(start, end);
            }

            //Return the total results of the filters
            this.total = this.myForData.filter((item) => {
              return item.employee_name
                .toLowerCase()
                .includes(this.search.toLowerCase());
            }).length;

            //Return the filtered data
            return this.myForData
              .filter((item) => {
                return item.employee_name
                  .toLowerCase()
                  .includes(this.search.toLowerCase());
              })
              .slice(start, end);
          },

          //Create array of all pages (for loop to display page numbers)
          pages() {
            return Array.from({
              length: Math.ceil(this.total / this.size),
            });
          },

          //Next Page
          nextPage() {
            this.pageNumber++;
          },

          //Previous Page
          prevPage() {
            this.pageNumber--;
          },

          //Total number of pages
          pageCount() {
            return Math.ceil(this.total / this.size);
          },

          //Return the start range of the paginated results
          startResults() {
            return this.pageNumber * this.size + 1;
          },

          //Return the end range of the paginated results
          endResults() {
            let resultsOnPage = (this.pageNumber + 1) * this.size;

            if (resultsOnPage <= this.total) {
              return resultsOnPage;
            }

            return this.total;
          },

          //Link to navigate to page
          viewPage(index) {
            this.pageNumber = index;
          },
        };
      }
    </script>
  </body>
</html>

Find more examples at: www.alpinetoolbox.com/examples