Introduction
Interactive applications provide a seamless user experience by updating content dynamically without full page reloads. This guide demonstrates how to build engaging, real-time web applications using AJAX techniques and the Jaxon library.
Interactive applications improve user experience by providing instant feedback, reducing wait times, and creating a more app-like feel in web browsers.
Key Principles
Asynchronous communication
Load and update data in the background without blocking user interactions.
Instant feedback
Provide immediate visual responses to user actions.
Progressive enhancement
Build applications that work without JavaScript but are enhanced with it.
State management
Track application state on both client and server sides.
Real-Time Chat Application
Let’s build a complete real-time chat application demonstrating key interactive patterns:
Complete Implementation
<?php
require (__DIR__ . '/../vendor/autoload.php');
use Jaxon\Jaxon;
use function Jaxon\jaxon;
$jaxon = jaxon();
// Function to handle incoming messages
function enviarMensaje($mensaje) {
// Sanitize the message to prevent XSS
$mensaje = strip_tags($mensaje);
// Add timestamp for better user experience
$timestamp = date('H:i:s');
$mensajeCompleto = "[{$timestamp}] {$mensaje}";
// Save to log file
file_put_contents('chat.log', $mensajeCompleto . PHP_EOL, FILE_APPEND);
}
// Function to retrieve and display chat history
function getLogDelChat() {
// Read chat log
$log = '';
if (file_exists('chat.log')) {
$log = file_get_contents('chat.log');
$log = preg_replace('/\r\n|\n|\r/', "<br>", $log);
}
// Create response and update DOM
$respuesta = jaxon()->newResponse();
$respuesta->assign('cajaDeChat', 'innerHTML', $log);
// Auto-scroll to bottom
$respuesta->script(
"document.getElementById('cajaDeChat').scrollTop = " .
"document.getElementById('cajaDeChat').scrollHeight;"
);
return $respuesta;
}
// Register functions
$jaxon->register(Jaxon::CALLABLE_FUNCTION, 'enviarMensaje');
$jaxon->register(Jaxon::CALLABLE_FUNCTION, 'getLogDelChat');
// Process requests
if($jaxon->canProcessRequest()) {
$jaxon->processRequest();
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Interactive Chat Application</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background: #f5f5f5;
}
h1 {
color: #333;
text-align: center;
}
#cajaDeChat {
border: 2px solid #007bff;
border-radius: 8px;
padding: 15px;
height: 400px;
overflow-y: auto;
margin-bottom: 15px;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
#chatForm {
display: flex;
gap: 10px;
}
#entrada {
flex: 1;
padding: 12px;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
#entrada:focus {
outline: none;
border-color: #007bff;
}
button {
padding: 12px 30px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: background 0.3s;
}
button:hover {
background: #0056b3;
}
button:active {
transform: translateY(1px);
}
.status {
text-align: center;
color: #666;
font-size: 12px;
margin-top: 10px;
}
.online {
color: #28a745;
}
</style>
</head>
<body>
<h1>Chat Interactivo con Jaxon</h1>
<div id="cajaDeChat"></div>
<form id="chatForm" onsubmit="return enviarMensaje()">
<input type="text"
id="entrada"
placeholder="Escribe un mensaje..."
autocomplete="off"
maxlength="200">
<button type="submit">Enviar</button>
</form>
<div class="status">
Estado: <span id="connectionStatus" class="online">Conectado</span>
</div>
<script>
// Send message function
function enviarMensaje() {
let mensaje = document.getElementById('entrada').value.trim();
// Validate message
if (mensaje === '') {
alert('Por favor, escribe un mensaje');
return false;
}
// Send message via Jaxon
jaxon_enviarMensaje(mensaje);
// Clear input
document.getElementById('entrada').value = '';
// Immediately update chat (optimistic UI update)
actualizarCajaChat();
return false;
}
// Update chat function
function actualizarCajaChat() {
jaxon_getLogDelChat();
}
// Poll for new messages every 2 seconds
let pollInterval = setInterval(actualizarCajaChat, 2000);
// Initial load
actualizarCajaChat();
// Focus input on load
document.getElementById('entrada').focus();
// Handle visibility change (pause polling when tab is hidden)
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
clearInterval(pollInterval);
} else {
pollInterval = setInterval(actualizarCajaChat, 2000);
actualizarCajaChat();
}
});
</script>
</body>
<?php
echo $jaxon->getJs();
echo $jaxon->getScript();
?>
</html>
Create forms that validate and submit without page reloads:
<?php
use Jaxon\Jaxon;
use function Jaxon\jaxon;
class FormularioContacto {
public function validarCampo($campo, $valor) {
$response = jaxon()->newResponse();
$esValido = false;
$mensaje = '';
switch($campo) {
case 'email':
$esValido = filter_var($valor, FILTER_VALIDATE_EMAIL);
$mensaje = $esValido ? '✓ Email válido' : '✗ Email inválido';
break;
case 'telefono':
$esValido = preg_match('/^[0-9]{9}$/', $valor);
$mensaje = $esValido ? '✓ Teléfono válido' : '✗ Formato: 9 dígitos';
break;
case 'nombre':
$esValido = strlen($valor) >= 3;
$mensaje = $esValido ? '✓ Nombre válido' : '✗ Mínimo 3 caracteres';
break;
}
// Update validation message
$response->assign("{$campo}_validacion", 'innerHTML', $mensaje);
// Update styling
if ($esValido) {
$response->addClass("{$campo}_validacion", 'valido');
$response->removeClass("{$campo}_validacion", 'invalido');
} else {
$response->addClass("{$campo}_validacion", 'invalido');
$response->removeClass("{$campo}_validacion", 'valido');
}
return $response;
}
public function enviarFormulario($datos) {
$response = jaxon()->newResponse();
// Process form data
// ... (save to database, send email, etc.)
// Show success message
$response->assign('resultado', 'innerHTML',
'<div class="success">¡Formulario enviado correctamente!</div>'
);
// Clear form
$response->script("document.getElementById('contactForm').reset();");
return $response;
}
}
$jaxon = jaxon();
$jaxon->register(Jaxon::CALLABLE_CLASS, FormularioContacto::class);
if($jaxon->canProcessRequest()) $jaxon->processRequest();
?>
<!DOCTYPE html>
<html>
<head>
<title>Formulario Interactivo</title>
<style>
.valido { color: green; }
.invalido { color: red; }
.campo { margin-bottom: 15px; }
input { padding: 8px; width: 100%; }
.validacion { font-size: 12px; margin-top: 5px; }
</style>
</head>
<body>
<h1>Formulario de Contacto</h1>
<form id="contactForm" onsubmit="return enviarFormulario()">
<div class="campo">
<label>Nombre:</label>
<input type="text"
id="nombre"
onblur="JaxonFormularioContacto.validarCampo('nombre', this.value)">
<div id="nombre_validacion" class="validacion"></div>
</div>
<div class="campo">
<label>Email:</label>
<input type="email"
id="email"
onblur="JaxonFormularioContacto.validarCampo('email', this.value)">
<div id="email_validacion" class="validacion"></div>
</div>
<div class="campo">
<label>Teléfono:</label>
<input type="tel"
id="telefono"
onblur="JaxonFormularioContacto.validarCampo('telefono', this.value)">
<div id="telefono_validacion" class="validacion"></div>
</div>
<button type="submit">Enviar</button>
</form>
<div id="resultado"></div>
<script>
function enviarFormulario() {
const datos = {
nombre: document.getElementById('nombre').value,
email: document.getElementById('email').value,
telefono: document.getElementById('telefono').value
};
JaxonFormularioContacto.enviarFormulario(datos);
return false;
}
</script>
<?php
echo $jaxon->getJs();
echo $jaxon->getScript();
?>
</body>
</html>
Live Search
Implement instant search results as users type:
<?php
class BuscadorProductos {
public function buscar($termino) {
$response = jaxon()->newResponse();
if (strlen($termino) < 2) {
$response->assign('resultados', 'innerHTML', '');
return $response;
}
// Search in database (simplified)
$productos = $this->buscarEnBD($termino);
$html = '<div class="search-results">';
if (count($productos) > 0) {
foreach ($productos as $producto) {
$html .= "<div class='producto'>";
$html .= "<h4>{$producto['nombre']}</h4>";
$html .= "<p>{$producto['descripcion']}</p>";
$html .= "<span class='precio'>{$producto['precio']}€</span>";
$html .= "</div>";
}
} else {
$html .= '<p>No se encontraron productos</p>';
}
$html .= '</div>';
$response->assign('resultados', 'innerHTML', $html);
return $response;
}
private function buscarEnBD($termino) {
// Database search implementation
// ...
return [];
}
}
?>
<input type="search"
id="buscador"
placeholder="Buscar productos..."
oninput="realizarBusqueda(this.value)">
<div id="resultados"></div>
<script>
let timeoutId;
function realizarBusqueda(termino) {
// Debounce search to avoid excessive requests
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
JaxonBuscadorProductos.buscar(termino);
}, 300);
}
</script>
Auto-Save Functionality
Automatically save user data as they type:
<?php
class EditorDocumento {
public function guardarBorrador($contenido, $documentoId) {
$response = jaxon()->newResponse();
// Save to database
$guardado = $this->guardarEnBD($documentoId, $contenido);
if ($guardado) {
$timestamp = date('H:i:s');
$response->assign('guardado_status', 'innerHTML',
"✓ Guardado automáticamente a las {$timestamp}"
);
$response->addClass('guardado_status', 'success');
} else {
$response->assign('guardado_status', 'innerHTML',
"✗ Error al guardar"
);
$response->addClass('guardado_status', 'error');
}
return $response;
}
private function guardarEnBD($id, $contenido) {
// Database save logic
return true;
}
}
?>
<textarea id="editor"
rows="10"
cols="50"
oninput="autoGuardar()">Tu contenido aquí...</textarea>
<div id="guardado_status"></div>
<script>
let autoSaveTimeout;
const DOCUMENTO_ID = 123; // From server
function autoGuardar() {
clearTimeout(autoSaveTimeout);
// Auto-save after 2 seconds of inactivity
autoSaveTimeout = setTimeout(() => {
const contenido = document.getElementById('editor').value;
JaxonEditorDocumento.guardarBorrador(contenido, DOCUMENTO_ID);
}, 2000);
}
</script>
Loading States and Feedback
Provide clear feedback during asynchronous operations:
// Show loading indicator before request
function realizarOperacion() {
// Show loading state
document.getElementById('boton').disabled = true;
document.getElementById('boton').innerText = 'Procesando...';
document.getElementById('spinner').style.display = 'block';
// Make Jaxon call
JaxonMiClase.operacionLarga();
}
// In PHP, hide loading state in response
$response->script(`
document.getElementById('boton').disabled = false;
document.getElementById('boton').innerText = 'Enviar';
document.getElementById('spinner').style.display = 'none';
`);
Best Practices
Debounce frequent operations
Use timeouts to limit the rate of AJAX requests for operations like search-as-you-type.
Provide visual feedback
Always show loading indicators and confirmation messages for user actions.
Handle errors gracefully
Display user-friendly error messages and provide recovery options.
Optimize polling
Pause polling when the page is not visible to conserve resources.
Validate on both sides
Perform validation on both client (for UX) and server (for security).
Implement optimistic updates
Update the UI immediately for better perceived performance.
Avoid excessive requests: Use debouncing and throttling techniques to limit the number of AJAX requests, especially for operations triggered by user input.
Cache strategically: Cache responses on the client side when appropriate to reduce server load and improve response times.
Accessibility
Ensure your interactive applications are accessible:
- Use ARIA attributes to announce dynamic content changes
- Maintain keyboard navigation functionality
- Provide alternative text for loading states
- Ensure form validation messages are screen-reader friendly
<div id="resultados"
role="region"
aria-live="polite"
aria-label="Search results">
</div>
Security Considerations
Sanitize input
Always sanitize and validate data received from client requests.
Implement CSRF protection
Use tokens to prevent cross-site request forgery attacks.
Rate limiting
Implement server-side rate limiting to prevent abuse.
Authentication checks
Verify user permissions for every AJAX request.
Testing Interactive Features
Test your interactive applications thoroughly:
- Test with slow network conditions
- Verify behavior when requests fail
- Test concurrent requests
- Check memory leaks in long-running applications
- Test with JavaScript disabled (progressive enhancement)
Next Steps
Continue learning:
- Explore WebSocket for true real-time communication
- Learn about Service Workers for offline functionality
- Study modern frameworks like React, Vue, or Alpine.js
- Investigate GraphQL for more efficient data fetching