miércoles, 15 de febrero de 2012

Como abandonar los frames de html en 3 simples pasos

En estos días tuve que hacer pequeñas modificaciones en los sitios web de ZinjaI y PSeInt, y esto me llevó a recordar lo poco recomendable que era utilizar frames html para armarlas. Tengo que aclarar que no se casi nada sobre lenguajes de programación y marcado para web. Es decir, entiendo como funcionan, pero no estoy realmente familiarizado con ninguno de ellos. Simplemente aprendí lo escensial de html y javascript durante un curso en mi carrera de grado y completé esta formación con un curso de php muy muy básico que tomé por separado (no tengo ni idea de la sintaxis para hacer una clase en php por ejemplo). Con este poco conocimiento y google a mano me las rebusco para armar los sitios webs de mis proyectos, ya que son sitios bastante simples, razón por la cual tampoco me tomé el tiempo de profundizar mucho sobre estos temas. Y entonces, como era de esperar, la implementación es horrible.

Basicamente me gustan los diseños claros: una pequeña cabecera que no moleste, un menú bien a mano, y el contenido al centro ocupando la mayor parte de la ventana. Una forma muy fácil de implementar esta organización en html es usando frames. Los frames básicamente particionan el espacio en varias sub-areas y dentro de cada una se carga una página web individualmente. Entonces, cuando empecé hace años a armar el sitio del pseint, dividí la ventana en tres: cabecera, menú y contenido, escribí un html para cada una y listo. Lo bueno de los frames es que cada área puede ser independiente y cargar una página completa hasta de otro sitio si se quiere. Además, al acceder a un enlace en el contenido por ejemplo se actualiza esa área, manteniendo el menú y la cabecera intactos.

Esto era bueno hace años cuando las velocidades de las redes no eran buenas. El esquema en html era básicamente el siguiente:

    <HTML>
        <HEAD>...</HEAD>
        <FRAMESET cols=200,* frameborder="NO" border="0" framespacing="0">
            <FRAME name=leftFrame noResize scrolling=no src="indice.php">
            <FRAMESET rows=32,* frameborder="NO" border="0" framespacing="0">
                <FRAME name=topFrame noResize scrolling=no src="titulo.php">
                <FRAME name=mainFrame src="portada.php">
            </FRAMESET>
        </FRAMESET>
    </HTML>

Hoy en día ya nadie aconseja el uso de frames, de hecho todo lo contrario, diría que está mal visto, y tampoco es conveniente ya que le complica la vida a los buscadores. Entonces me propuse hacer el cambio, pero con el menor esfuerzo posible. Para empezar, ¿cambiamos a que?. A divs y css, claro, no?. Bien, entonces para armar un esquema como el que tenía con divs, el código podría ser:

    <HTML>
        <HEAD>...</HEAD>
        <BODY>
            <div style="position:absolute;width:99%;z-index:1;">
                ...titulo...
            </div>
            <div style="position:absolute;width:180px;z-index:2;">
                ...menu...
            </div>
            <div style="position:absolute;left:200px;top:50px;z-index:3;">
                ... contenido...
            </div>
        </BODY>
    </HTML>

El primer problema es que tendría que reemplazar ...titulo..., ...menú... y ...contenido... por lo que antes tenía en mis htmls, y cada página que el usuario carga tendría que ser un gran documento con las tres cosas. Está claro que copiar y pegar el título, el menú y el esquema de divs en cada página de contenido no es una buena idea. Lo mejor sería que se generase dinámicamente, y aquí es donde empezamos a poner un poquito de php. Resulta que buscando en google encuentro que en php tenemos dos instrucciones, include y request (varian solo en el manejo de errores), que funcionan como si fueran un #include en C/C++, es decir, insertando el contenido de un archivo dentro de otro como si siempre ubiese estado ahí. Entonces, reemplazando por ejemplo ...titulo... por include "titulo.php" colocamos el mismo html que tenía para los frames, ahora en mi esquema de divs (o casi, en realidad a ese html hay que estirparle la cabecera y algunas etiquetas). El problema es qué hacer con contenido, ya que debería ir cambiando la página que se incluye. Este primer problema lo solucioné haciendo que mi página principal (la que contiene este esquema) reciba en la url como argumento tipo get el nombre del archivo a incluir en la parte de contenido. El código de los divs (que supondremos en un archivo index.php y el argumento es page) entonces queda como sigue:
   
    <div style="position:absolute;width:99%;z-index:1;">
        <?php include "titulo.html"; ?>
    </div>
    <div style="position:absolute;width:180px;z-index:2;">
        <?php include "menu.html"; ?>
    </div>
    <div style="position:absolute;left:200px;top:50px;z-index:3;">
        <?php include $_GET['page']; ?>
    </div>

Pero aparecen nuevos problemas. Por un lado un tema de seguridad, habría que controlar que nadie pase en el argumento page una dirección de un archivo que no debería poder ver. Para esto habrá que agregar unos ifs analizando el contenido. Por otro lado, absolutamente todas las páginas de contenido que quiera mostrar deberían pasar a través de este archivo. Entonces, si una página de contenido tenía un link a "descargas.html", ahora debe apuntar a "index.php?page=descargas.html". Nuevamente google me dió una buena solución. Resulta que php también tiene una par de funciones para que la salida del script vaya temporalmente a un buffer en lugar de ir directamente al navegador. Entonces, enviamos la salida del include al buffer, buscamos los links en el buffer, todo lo que empieza con href=", y los actualizamos reemplazando ese comienzo por href="index.php?page=. El resultado sería algo así:

    // hacer que la salida se guarde en el buffer
    ob_start();
    // incluir la pagina
    include($_GET['page']);
    // obtener el contenido del buffer
    $out = ob_get_clean();
    // actualizar los enlaces
    $out = str_replace("href=\"http://","href_=\"http://",$out);
    // escribir el contenido final
    echo $out;

Simpático, ¿no?. Pero aún quedan problemas. Uno a analizar es: ¿qué pasa cuando el link al que apuntamos también era un script de php que tenía sus propios argumentos?. La URL quedará mal formada ya que aparecerá dos veces el signo de interrogación (?), y la página de contenido, si llega a cargarse, podría no recibir sus argumentos. Pero leyendo encuentro que, con toda lógica, el código incluido con include o request hereda el ambiente de quien lo incluye. Es decir, si index recibe argumentos, las página incluidas pueden acceder a ellos como siempre gracias a que al procesarla, el ambiente hereda la variable $_GET. Así que alcanza con arreglar la url cambiando el signo que marcaba el comienzo de argumentos (?) por el que separa una lista de argumentos en un request que utiliza get (&). Esto sería, por ejemplo, reemplazar la url "ejemplos.php?cual=matematicas.psc" por "index.php?page=ejemplos.php&cual=matematicas.psc", tarea que se resuelve facilmente agregando otro str_replace, pero ojo que debe ir antes que el reemplazo que ya estaba para que no modifique el comienzo de los argumentos para index.html.

Finalmente, el último problema es qué hacer con los enlaces a sitios externos (por ejemplo a otros proyectos, a las descargas de sourceforge, etc.). Mi idea fue identificarlos suponiendo que las urls externas se escriben completas (empezando con http://...") y las del propio sitio no. Entonces, href="http://sourceforge.net/....blabla..." se debe dejar tal cual para que se muestre sin pasar por index.php y sus divs, mientras que href="imagenes.php" se debe cambiar por href="index.php?page=imagenes.php" como propuse antes. Para esto, lo que hago es agregar dos reemplazos más. Un primer reemplazo cambia todo lo que empieza con href="http:// por otra cosa que no empiece con href=" para que el reemplazo que agrega index.php?page= no lo considere. Luego, los reemplazos ya presentados, y finalmente se restauran los links modificados en el primero. Entonces queda:

<?php
    // redireccionar salida a un buffer
    ob_start();
    // incluir la pagina de contenido
    include($_GET['page']);
    // obtener su contenido desde el buffer
    $out = ob_get_clean();
    // modificar temporalmente los enlaces a pag externas agregando un guino bajo a href
    $out = str_replace("href=\"http://","href_=\"http://",$out);
    // arreglar las urls con argumentos get
    $out = str_replace(".php?",".php&",$out);
    // hacer que lo enlaces locales pasen a travez de index.php
    $out = str_replace("href=\"","href=\"index.php?page=",$out);
    // restablecer los enlaces a sitios externas removiendo el guion bajo
    $out = str_replace("href_=\"http://","href=\"http://",$out);
    // escribir el contenido final
    echo $out;
?>

De esta forma, solo tuve que reemplazar el archivo que contenía los frames por este nuevo archivo que contiene el código para los divs y los includes, y extraer las cabeceras (sección head y etiquetas html y body) de los demás. Ya sé que si alguien que de verdad trabaja con php, html, css, etc lee esto probablemente dirá que hay muchas burradas y desprolijidades, pero cumplí mi objetivo, me deshice de los frames con poco esfuerzo sin modificar la estructura del sitio. Todavía no subí las nuevas páginas al sitio, pero en unos pocos días verán los resultados... o no, la idea es que casi no cambie.

6 comentarios:

  1. Hola Admin! al igual que yo empeze en esto de las webs tube muchos problemas y no encontraba ayuda de nadie para mis scripts, bueno dices que igual "algun programador lee esto probablemente dirá que hay muchas burradas y desprolijidades" pues tengo que decirte que es un buen trabajo, errores quiza los haya o no pero como bien dices lograste tu objetivo y creo que antes que criticar si bien o mal hay que mirar mas el esfuerzo que cada uno pone en sus cometidos, y eso creo que supera con creces cualquier comentario negativo.

    He dixo....

    ResponderEliminar
  2. me quede ciego luego de leeer todo, con ese fondo oscuro y las letras blancas, pero el contenido bueno gracias =)

    ResponderEliminar
  3. yo soy de HTML5 "pa ca" pero me impreciona como soluciono estos problemas y de hecho las paginas se ven muy bien ... saludos

    ResponderEliminar
  4. Todo funciona hasta que se habla del tema de seguridad y vincular un archivo mágico. De ahí en adelante no se entiende nada.

    De todas manera me dio una muy buena idea la primera parte.

    ResponderEliminar
  5. brother. espero aun sigas vivo :v.
    sabes como sacar el enlace de un frame para hacerle una redireccion por medio de una accion que va en la redireccion?

    te lo ponde masomenos en codigo como va w=id lo puse asi porque me lo bloqueaba
    <(src="google.js" (aqui va el size) w="autoid">

    bueno en si necesito sacar el enlace del frame para que quede en la parte donde dice google, y el autoid es el click automatico hecho con javascript,esto sirve para que se haga una redireccion automatica. no le puse target="_blank" porque el navegador lo detecta como popup y lo bloquea espero puedas ayudarme o darme una solucion porfa

    ResponderEliminar
    Respuestas
    1. No termino de entender del todo la pregunta, pero igual no creo que pueda ayudarte mucho ya que mi fuerte no pasa por lo web (sino por lo desktop). Esto lo armé copiando y modificando ejemplos, no uso HTML ni PHP regularmente.

      Pero en cualquier caso, las soluciones que planteo pasan por usar php para alterar los fuentes html... entonces todo se reduce a operaciones sobre strings... hay que buscarle la vuelta por ese lado. Es decir, cómo identificar el caso (patrón a buscar en el código), y cómo reescribirlo (plantilla para el reemplazo).

      Eliminar