Skip to content

Instantly share code, notes, and snippets.

@cdsaenz
Last active May 15, 2023 17:10
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cdsaenz/c331b81c1639adb5921da4ffc9408144 to your computer and use it in GitHub Desktop.
Save cdsaenz/c331b81c1639adb5921da4ffc9408144 to your computer and use it in GitHub Desktop.
WordPress Template for JavaScript Blog Articles Load with Infinity Scroll
<?php
/**
* Template Name: Blog Infinity
* CSDev
* Assign to a page called "Blog" or something
* Requires Bootstrap 5 but can be easily refactored to your Stylesheet
*/
add_action('wp_head', function () {
?>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<?php
});
get_header();
?>
<style>
.spinner-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.spinner-container:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.spinner {
border: 16px solid #f3f3f3;
border-top: 16px solid #316275;
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.post-thumbnail-placeholder {
width: 400px;
height: 400px;
background-color: #eee;
display: flex;
justify-content: center;
align-items: center;
}
.post-thumbnail-placeholder p {
color: #999;
font-size: 16px;
font-weight: bold;
}
</style>
<div id="content" class="site-content">
<main id="main" class="site-main">
<!-- Title & Description -->
<header class="page-header mb-4 text-center px-5 pb-4 mx-5">
<div class="text-center">
<h1 class="header-title">
<?php the_title() ?>
</h1>
<div class="magazine-subtitle">
<?php the_content() ?>
</div>
</div>
</header>
<!-- Alpine Posts -->
<div x-data="postsInit()" x-init="initData()">
<!-- Category filter -->
<div class="container">
<div class="row">
<div class="col-12 col-md-4 pb-1 pb-md-5" id="category-filter">
<span class="d-inline-block me-2">
<label for="category-select"><?= __('Category','csdev') ?></label>
</span>
<!-- Categories dropdown -->
<div class="d-inline-block">
<select name="category-select" id="category-select" x-model="selectedCategory" @change="loadWithNewCategory()">
<option value=""><?= __('All Categories', 'csdev') ?></option>
<template x-for="category in categories" :key="category.id">
<option :value="category.id" x-text="category.name"></option>
</template>
</select>
</div>
</div>
</div>
</div>
<!-- Posts -->
<template x-for="post in posts">
<div class="card horizontal border-0 mb-4">
<div class="row">
<!-- Meta -->
<div class="col-12 col-md-2 text-center">
<p class="badge rounded-pill bg-light text-dark" x-text="post.id"></p>
<p x-text="post.modified.substring(0, 10)"></p>
<!-- categories here -->
<div class="category-badge mb-2">
<template x-for="(categoryName, index) in post.categoriesNames" :key="index">
<span x-text="categoryName" class="badge text-bg-secondary bg-opacity-25 text-decoration-none"></span>
</template>
</div>
</div>
<!-- Featured Image-->
<div class="col-12 col-md-3">
<div class="blog-post-image">
<template x-if="post.thumbnail">
<img :src="post.thumbnail" :alt="post.title.rendered" />
</template>
<template x-if="!post.thumbnail">
<div class="post-thumbnail-placeholder rounded">
<p><?= __('No image available', 'csdev') ?></p>
</div>
</template>
</div>
</div>
<!-- Titulo -->
<div class="col-12 col-md-5">
<div class="card-body">
<!-- Title -->
<h2 class="h3 blog-post-title">
<a :href="post.link">
<span x-text="post.title.rendered"></span>
</a>
</h2>
<!-- Excerpt & Read more -->
<div class="card-text mt-auto fst-italic">
<span x-html="post.excerpt.rendered"></span>
<a class="read-more" :href="post.link">
<?php _e('Read More', 'csdev'); ?>
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<!-- Load More Button - alternative to Infinite Scroll -->
<template x-if="nextPage <= totalPages && !isLoading">
<div class="text-center">
<button @click.prevent="fetchPosts(nextPage)" class="btn btn-lg btn-primary">
<?= __('Load More', 'csdev') ?>
</button>
</div>
</template>
<!-- Spinner -->
<div x-show="isLoading" class="spinner-container">
<div class="spinner"></div>
</div>
<!-- Infinite Scroll Observed -->
<div id="loader"></div>
</div>
</main><!-- #main -->
</div>
<?php get_footer(); ?>
<script>
function postsInit() {
return {
isLoading: true,
posts: [],
categories: [],
selectedCategory: "",
currentPage: 0,
nextPage: 0,
totalPages: 0,
async initData() {
await this.fetchCategories();
this.fetchPosts(1);
this.setupInfiniteScroll();
},
/* ASYNC Fetch categories from WP REST API */
async fetchCategories() {
let catsURL = '<?= rest_url('wp/v2/') ?>categories';
const response = await fetch(catsURL);
const data = await response.json();
this.categories = data.filter(category => category.count > 0);
},
/* reload but clear everything */
loadWithNewCategory() {
this.posts = [];
this.fetchPosts(1, this.selectedCategory);
},
/* obserer */
setupInfiniteScroll() {
// Create a new Intersection Observer instance
const observer = new IntersectionObserver((entries) => {
// When the observed element enters the viewport, load more posts
if (entries[0].isIntersecting) {
if (this.nextPage <= this.totalPages && !this.isLoading) {
this.fetchPosts(this.nextPage);
}
}
}, {
// Set the threshold to 0 to get it triggered when any part is visible
// or adjust the margins below
rootMargin: '0px 0px 100px 0px',
});
// Observe the element at the bottom of the page
const loader = document.getElementById('loader');
observer.observe(loader);
},
/* load EXTRA posts for a page (and category if any selected )*/
fetchPosts(page) {
let postsURL = `<?= rest_url('wp/v2/') ?>posts?per_page=2&page=${page}&_embed`;
let mediaURL = '<?= rest_url('wp/v2/') ?>media';
if (this.selectedCategory) {
postsURL = `<?= rest_url('wp/v2/') ?>posts?per_page=2&page=${page}&categories=${this.selectedCategory}&_embed`;
}
this.isLoading = true;
fetch(postsURL)
.then(res => {
this.totalPages = res.headers.get('X-WP-Totalpages');
if (res.ok) {
return res.json();
} else {
throw new Error('Error fetching data');
}
})
.then(data => {
// Add the posts to the array
data.forEach(post => {
// Add the thumbnail URL to the post object
if (post._embedded['wp:featuredmedia'] && post._embedded['wp:featuredmedia'][0].source_url) {
post.thumbnail = post._embedded['wp:featuredmedia'][0].source_url;
} else {
post.thumbnail = false;
}
// add category name(s) to post
post.categoriesNames = post.categories.map(catId => {
const cat = this.categories.find(cat => cat.id === catId);
return cat ? cat.name : '';
});
this.posts.push(post);
});
this.currentPage = page;
this.nextPage = this.currentPage + 1;
/* A little wait so it's all rendered */
setTimeout(() => {
this.isLoading = false;
}, 500);
});
}
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment