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
Advertisment
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