Contents
Introducción
Este tutorial explica cómo paginar comentarios de WordPress dinámicamente usando JavaScript (AJAX/Fetch) y un endpoint REST personalizado que devuelve HTML de los comentarios ya formateados. La idea es mantener la marca visual y la compatibilidad con los callbacks de comentarios de WordPress (como wp_list_comments) pero actualizar la página sin recargar por completo. El resultado: navegación fluida entre páginas de comentarios, soporte para la historia del navegador (back/forward), y carga progresiva para mejorar UX.
Requisitos y consideraciones
- WordPress 4.7 (para REST API), idealmente 5.x.
- Acceso al functions.php del tema o plugin propio para registrar endpoints y encolar scripts.
- Conocimientos básicos de JavaScript moderno (Fetch, Promises) y PHP.
- Accesibilidad: los enlaces de paginación deben ser focusables y anunciables por lectores de pantalla.
- Fallback: asegúrate de que la paginación HTML tradicional siga funcionando si JavaScript está deshabilitado.
Enfoque general
- Crear un endpoint REST que acepte parámetros: post_id, page y per_page, y que devuelva HTML con la lista de comentarios y datos de paginación (total, total_pages).
- Encolar un script JavaScript en el frontend que capture los clics en los enlaces de paginación y haga llamadas Fetch al endpoint, reemplace el contenedor de comentarios y actualice la URL con history.pushState.
- Agregar manejo de estados (cargando, error) y restaurar el estado al navegar con back/forward (window.onpopstate).
- Opcional: añadir caching simple en el cliente y optimizaciones en el servidor (transients) para evitar consultas repetidas.
1) Código del endpoint REST (PHP)
Registrar un endpoint en REST API que reciba parámetros y devuelva un array JSON con html, page, total_pages y total_comments. Este endpoint renderizará los comentarios con wp_list_comments para mantener el markup del tema.
GET,
callback => custom_rest_get_comments_html,
permission_callback => __return_true, // ajustar si es necesario
))
})
function custom_rest_get_comments_html(WP_REST_Request request) {
post_id = absint(request->get_param(post_id))
page = max(1, intval(request->get_param(page) ?: 1))
per_page = max(1, intval(request->get_param(per_page) ?: get_option(comments_per_page, 10)))
order = sanitize_text_field(request->get_param(order) ?: get_option(comment_order, asc))
if (!get_post(post_id)) {
return new WP_Error(invalid_post, Post not found, array(status => 404))
}
// Obtener total de comentarios aprobados para ese post
count_args = array(
post_id => post_id,
status => approve,
count => true,
)
total_comments = get_comments(count_args)
total_pages = (int) ceil(total_comments / per_page)
// Obtener comentarios para la página solicitada
comments_args = array(
post_id => post_id,
status => approve,
number => per_page,
offset => (page - 1) per_page,
orderby => comment_date_gmt,
order => order,
)
comments = get_comments(comments_args)
// Renderizar los comentarios usando el walker del tema
ob_start()
// El container se puede personalizar en el tema aquí usamos wp_list_comments
// Asegúrate que tu tema tiene el callback correcto para wp_list_comments
wp_list_comments(array(style => ol, short_ping => true), comments)
comments_html = ob_get_clean()
// Generar HTML para la paginación (links simples) el JS puede procesarlos
base = add_query_arg(array(page => %#%), get_permalink(post_id))
pagination_html = paginate_links(array(
base => base,
format => ?page=%#%,
current => page,
total => total_pages,
type => list,
end_size => 1,
mid_size => 2,
))
return rest_ensure_response(array(
html => comments_html,
pagination_html => pagination_html,
page => page,
total_pages => total_pages,
total_comments => total_comments,
))
}
?>
Notas sobre el endpoint
- La función wp_list_comments utiliza el callback de tu tema para renderizar cada comentario. Si tu tema no lo define, WordPress usa el por defecto.
- Se puede mejorar la seguridad restringiendo permission_callback o validando nonces si devuelve datos sensibles.
- Si prefieres devolver datos crudos (JSON) en lugar de HTML, el cliente debe renderizar los comentarios aquí optamos por HTML para preservar markup complejo.
2) Encolar scripts y pasar variables al cliente
Encolar el archivo JS y pasar las variables necesarias (endpoint URL, post ID, nonce). Usa wp_localize_script o wp_add_inline_script para inyectar configuración.
esc_url_raw(rest_url(custom/v1/comments)),
nonce => wp_create_nonce(wp_rest),
per_page=> (int) get_option(comments_per_page, 10),
))
})
?>
3) Código JavaScript: lógica del cliente
El JS captura clics en enlaces de paginación dentro de un contenedor (por ejemplo #comments-pagination) y solicita la nueva página al endpoint. También maneja history.pushState y window.onpopstate para navegación con botones del navegador.
// Archivo: js/comments-pager.js
(function () {
if (!(fetch in window)) return // Fallback simple
var settings = window.CommentsPagerSettings {}
var apiUrl = settings.api_url
var perPage = settings.per_page 10
var commentsContainer = document.getElementById(comments) // debe existir en tema
var paginationContainer = document.getElementById(comments-pagination) // donde están los links
var currentPostId = commentsContainer ? commentsContainer.getAttribute(data-post-id) : null
if (!commentsContainer !paginationContainer !currentPostId) return
function buildUrl(page) {
var url = new URL(apiUrl, window.location.origin)
url.searchParams.set(post_id, currentPostId)
url.searchParams.set(page, page)
url.searchParams.set(per_page, perPage)
return url.toString()
}
function setLoading(state) {
if (state) {
commentsContainer.classList.add(loading)
// puedes insertar un spinner si quieres
} else {
commentsContainer.classList.remove(loading)
}
}
function fetchPage(page, pushState) {
setLoading(true)
fetch(buildUrl(page), {
method: GET,
credentials: same-origin,
headers: {
X-WP-Nonce: settings.nonce
}
})
.then(function (res) {
if (!res.ok) throw new Error(Error en la solicitud: res.status)
return res.json()
})
.then(function (data) {
// Reemplazar HTML de comentarios
commentsContainer.innerHTML = (data.html )
// Reemplazar paginación
if (data.pagination_html) {
paginationContainer.innerHTML = data.pagination_html
} else {
paginationContainer.innerHTML =
}
// Actualizar el estado de la URL (query param cpage por ejemplo)
var newUrl = new URL(window.location.href)
newUrl.searchParams.set(cpage, page)
if (pushState) {
history.pushState({ cpage: page }, , newUrl.toString())
} else {
history.replaceState({ cpage: page }, , newUrl.toString())
}
// Opcional: foco en container para a11y
commentsContainer.setAttribute(tabindex, -1)
commentsContainer.focus()
})
.catch(function (err) {
console.error(err)
// mostrar mensaje de error mínimo
paginationContainer.insertAdjacentHTML(beforeend, No se pudieron cargar los comentarios. Inténtalo de nuevo.
)
})
.finally(function () {
setLoading(false)
})
}
// Manejar clics delegados en el contenedor de paginación
paginationContainer.addEventListener(click, function (e) {
var target = e.target
// Buscar el enlace hasta el ancestor
while (target target !== paginationContainer target.tagName !== A) target = target.parentNode
if (!target target === paginationContainer) return
e.preventDefault()
// Determinar página objetivo desde href o texto
var href = target.getAttribute(href)
var pageMatch = null
if (href) {
try {
var u = new URL(href, window.location.origin)
pageMatch = u.searchParams.get(page) u.searchParams.get(cpage)
} catch (err) {
// parse fallback: buscar ?page=#
var m = href.match(/page=(d )/)
if (m) pageMatch = m[1]
}
}
var page = parseInt(pageMatch, 10) 1
fetchPage(page, true)
})
// Manejar back/forward
window.addEventListener(popstate, function (e) {
var state = e.state
var page = state state.cpage ? state.cpage : (new URL(window.location.href)).searchParams.get(cpage) 1
fetchPage(page, false)
})
// Inicial: si URL tiene cpage, cargar esa página
var initialPage = (new URL(window.location.href)).searchParams.get(cpage) 1
// Si la página inicial no es 1, solicitarla para asegurar que el HTML coincide
if (parseInt(initialPage, 10) !== 1) {
fetchPage(initialPage, false)
}
})()
Explicación de decisiones en JS
- Uso de fetch con credentials para soportar cookies si es necesario.
- Encapsulado y delegación de eventos para funcionamiento con paginaciones generadas dinámicamente.
- Uso de history.pushState para actualizar la URL sin recargar y window.onpopstate para restaurar páginas al usar atrás/adelante.
- Foco en el contenedor tras el cambio para accesibilidad.
4) Ejemplo de HTML del tema (marcadores para JS)
Tu plantilla de comentarios (comments.php o template part) debe ofrecer contenedores con identificadores utilizados por el script. Un ejemplo sencillo:
>
5) Estilos básicos (CSS)
Agregar un estilo de carga y visuales mínimos para indicar que se está cargando.
/ Ejemplo simple /
#comments.loading { opacity: 0.6 position: relative }
#comments.loading:after {
content: Cargando...
position: absolute
right: 1rem
top: 1rem
background: rgba(0,0,0,0.6)
color: #fff
padding: 0.2rem 0.5rem
border-radius: 3px
font-size: 0.9rem
}
.comments-error { color: #c00 margin-top: 1rem }
6) Mejores prácticas y optimizaciones
- Cache en servidor: si tu site recibe muchas peticiones, considera usar transients para almacenar HTML de páginas de comentarios por X segundos.
- Evita consultas innecesarias: limita metadatos cargados por comentario si no los necesitas.
- Paginación accesible: usa enlaces ltagt con texto claro (Página 2) y atributos ARIA si procede.
- SEO y enlaces profundos: actualizar la URL permite enlazar a una página de comentarios concreta. También añade rel=prev/next si generas links en servidor.
- Fallback sin JS: conserva la paginación tradicional en HTML para usuarios sin JavaScript.
- Protección contra scraping: si expones datos sensibles o emails en comentarios, sanitiza y evita exponer información no permitida.
7) Manejo de errores comunes
- 500 o error REST: revisa logs PHP y confirma que el endpoint se registró correctamente y no hay errores de sintaxis.
- Comentarios vacíos: si no hay comentarios, asegúrate de que la respuesta devuelva HTML vacío y la UI muestre un mensaje amigable.
- Problemas con el markup: si el tema usa un walker personalizado, verifica que wp_list_comments esté recibiendo los argumentos correctos y que el markup resultante sea válido.
- Nonce incorrecto: si obtienes 403, revisa que estás enviando el header X-WP-Nonce y que se crea con wp_create_nonce(wp_rest).
8) Extensiones y variaciones posibles
- Infinite scroll: en lugar de paginación, cargar la siguiente página automáticamente al llegar al final.
- Renderizado parcial: devolver solo datos JSON y construir cada comentario en el cliente (útil para apps SPA o cuando necesitas reuso extremo).
- Filtrado y orden: permitir parámetros de búsqueda, autor o orden en el endpoint para funcionalidad avanzada.
- Comentarios anidados: si tu tema utiliza comentarios anidados profundos, asegúrate de que wp_list_comments preserve la estructura.
Conclusión
La paginación dinámica de comentarios en WordPress mejora significativamente la experiencia del usuario manteniendo la semántica y el markup original del tema cuando el servidor devuelve HTML. Con un endpoint REST personalizado, un script cliente que gestione Fetch y pushState, y un fallback HTML tradicional, obtendrás una solución robusta, accesible y amigable para SEO. Implementa cache en servidor y validaciones de seguridad según sea necesario para cargas altas o requisitos específicos.
|
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |
