Cargando la web de forma asíncrona pero sin URL – javascript php jquery

Pregunta:


Estoy desarrollando una web para la gestión de pacientes.

La web tiene un solo punto de entrada (digamos que es index.php). Ese landing dibuja un cierto HTML que contiene un menú y un div de contenido.

Dependiendo del elemento del menú que es activado, se dispara una llamada por Ajax que llena el div de contenido.

$(document).ready(function(){

    $.ajax({ url: "formulario.php", 
        dataType: 'text',
        success: function(data) {
            $("#sentences").html(data); 
        }, 
        error: function() {
            alert("error"); 
        }
    });
});

Como todo esto ocurre sin cambiar la URL, no tengo cómo definir una URL única para una sección en particular. Cualquier visitante que llegue al sitio verá simplemente el landing por defecto.

¿Hay alguna solución práctica para mantener el refresco dinámico del contenido, pero al mismo tiempo darle a cada sección una URL única?

Preguntado por: sergibarca

Respuesta Alvaro Montoro:

Usa almohadillas/hash (el símbolo #) para diferenciar cada sección de las demás. Así por ejemplo si tienes las secciones “formulario”, “contacto”, “historia”, etc. Tendrían URLs únicas:

  • index.php#formulario
  • index.php#contacto
  • index.php#historia

Y seguirías cargando tu menú de la misma manera a como lo estás haciendo ahora (o podrías cambiarlo para que fueran enlaces a #formulario, #contacto, etc. y detectar el cambio usando un controlador del evento onhashchange).

Lo único sería que ahora los usuarios podrían acceder a una sección particular usando su “deep link“. Entonces deberías cambiar un poco el código que se ejecuta al principio para que si hay un hash lo detecte y cargue la página correcta en lugar de la página por defecto. El cambio sería sencillo: leerías el hash (usando window.location.hash) y con un switch elegirías la url a cargar. Algo como esto:

$(document).ready(function(){

    var myURL = "formulario.php";

    if (window.location.hash) {
       switch (window.location.hash) {
            case "#historia": myURL = "historia.php"; break;
            case "#contacto": myURL = "contacto.php"; break;
       }
    }

    $.ajax({ url: myURL, 
        dataType: 'text',
        success: function(data) {
            $("#sentences").html(data); 
        }, 
        error: function() {
            alert("error"); 
        }
    });
});

Ejemplo funcionando

Voy a hacer un ejemplo básico con 4 páginas (la principal y 3 subpáginas que se cargaran via AJAX). Dejo todo el código aquí, pero como no ejecuta PHP, he creado una demo online en un servidor propio para que lo puedas ver funcionando.

index.php (página principal):

<!doctype html>
<html>
    <head>
        <title>Test Hash</title>
    </head>
    <body>
        <div id="opciones">
            <a href="#formulario">Formulario</a>
            <a href="#contacto">Contacto</a>
            <a href="#historia">Historia</a>
        </div>
        <div id="sentences"></div>

        <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
        <script>

            function hashChange() {

                var myURL = "formulario.php";

                if (window.location.hash) {
                    switch (window.location.hash) {
                        case "#contacto": myURL = "contacto.php"; break;
                        case "#historia": myURL = "historia.php"; break;
                        case "#formulario":
                        default: myURL = "formulario.php"; break;
                    }
                }

                $.ajax({ 
                    url: myURL,
                    dataType: 'text',
                    success: function(data) {
                        $("#sentences").html(data); 
                    }, 
                    error: function() {
                        alert("error"); 
                    }
                });
            }

            $(document).ready(function(){
                hashChange();
                $(window).on("hashchange", function() {
                    hashChange();
                });
            });
        </script>
    </body>
</html>

formulario.php:

<h1>Titulo Formulario</h1>
<p>Contenido formulario</p>

historia.php:

<h1>Historia</h1>
<p>Contenido Historia</p>

contacto.php:

<h1>Contacto</h1>
<p>Contenido Contacto</p>

Puedes ver que cuando se carga la página, se hará con el contenido de la sección indicada en el hash (si ninguno se especifica, se mostrará el formulario). Además, al cambiar la URL con cada interacción del menú, puedes pulsar los botones de adelante y atrás porque se guardarán los cambios en el historial del navegador.

Se me ocurren unas cuántas formas, una es la que te dijo @AlvaroMontoro; otra es mediante la History API y otra es usando un router en la parte cliente.

History API

Esta API es muy práctica e intuitiva de usar. Tienes algunos métodos a disposición:

  • pushState
  • replaceState

El método que nos interesa más es pushState. Este método añade una entrada a la historia del navegador. Supongamos que haces click en Home, luego en Portafolio y por último en Contáctame, entonces, la pila de historia para tu dominio será:

  • Home -> Portafolio -> Contáctame

Y si haces click en atrás del navegador seguirás ese orden en modo reverso.

El método pushState no comprueba si la URL o página que se le indica existe o no. Este método solo la añade al stack de historia. Este método recibe tres parámetros: objeto de estado (para almacenar información respecto a la URL a cargar), el título del documento (ignorado por algunos navegadores) y la nueva URL (relativa) a mostrar.

$(document).ready(function () {
  $('#menu a').on('click', function (e) {
    e.preventDefault();
    var menuName = $(this).text();

    $.ajax({
      url: "formulario.php?page" + menuName,
      dataType: 'text',
    })
    .done(function (html) {
      $('#sentences').html(html);
      history.pushState(
        {},
        menuNames.charAt(0).toUpperCase() + string.slice(1),
        menuName
      );
    })
    .fail(function (xhr, status, error) {
      // hacer algo
    });
  });
});

El código anterior hace lo siguiente: cada vez que se haga click en un menú, se obtiene le HTML de la sección que indica el menú por medio del parámetro ?page el cual se recibe en el backend y se envía una vista de acuerdo a ella. Finalmente, actualiza la URL de modo que si:

  • Vas a Home: la URL será /tuapp/home.
  • Vas a Portafolio: la URL será /tuapp/portafolio.
  • Vas a Contáctame: la URL será tuapp/contáctame

Usando un Router del lado cliente

Tienes algunas opciones aquí, como Page.js y Navigo. Me gusta más Page.js porque se parece mucho al router de Vue.js. El mismo ejemplo que el anterior puede hacerse con Page.js:

$(document).ready(function () {
  page.base('/app'); // nombre de tu app
  page('/home', render);
  page('/portafolio', render);
  page('/contactame', render);
  page(); // inicializa page.js
});

function render (ctx, next) {
  let pageName = ctx.pathname.split('/').reverse()[0];

  $.ajax({
    url: "formulario.php?page=" + pageName,
      dataType: 'text',
  })
  .done(function (html) {
    $('#sentences').html(html);
  })
  .fail(function (xhr, status, error) {
    // hacer algo
  });
});

En este caso, no es necesario prevenir las acciones por defecto de los links ya que de eso se encarga Page.js. Solo debes definir tu ruta base (<base href="/tuapp/">) y encargarte de traer el HTML e insertarlo en el documento.

Demo

<!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>Demo Page.js</title>
  <base href="/app/">
</head>
<body>

  <a href="home">Home</a>
  <a href="portafolio">Portafolio</a>
  <a href="contactame">Contactame</a>
  
  <div id="view-router"></div>
  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/page.js/1.7.1/page.min.js"></script>
  <script>
    document.addEventListener('DOMContentLoaded', function () {
      page.base('/app');
      page('/home', render);
      page('/portafolio', render);
      page('/contactame', render);
      page();
    });

    function render (ctx, next) {
      let viewRouter = document.getElementById('view-router');
      let pageName = ctx.pathname.split('/').reverse()[0];
      viewRouter.innerHTML = `Estás viendo ${pageName}`;
    }
  </script>
</body>
</html>

Fuente

One Comment

Add a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *