Skip to main content

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

1

Asynchronous communication

Load and update data in the background without blocking user interactions.
2

Instant feedback

Provide immediate visual responses to user actions.
3

Progressive enhancement

Build applications that work without JavaScript but are enhanced with it.
4

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

index.php
<?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>

Dynamic Form Processing

Create forms that validate and submit without page reloads:

Interactive Form Example

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

1

Debounce frequent operations

Use timeouts to limit the rate of AJAX requests for operations like search-as-you-type.
2

Provide visual feedback

Always show loading indicators and confirmation messages for user actions.
3

Handle errors gracefully

Display user-friendly error messages and provide recovery options.
4

Optimize polling

Pause polling when the page is not visible to conserve resources.
5

Validate on both sides

Perform validation on both client (for UX) and server (for security).
6

Implement optimistic updates

Update the UI immediately for better perceived performance.

Performance Considerations

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

1

Sanitize input

Always sanitize and validate data received from client requests.
2

Implement CSRF protection

Use tokens to prevent cross-site request forgery attacks.
3

Rate limiting

Implement server-side rate limiting to prevent abuse.
4

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