Alters

Modificar diseño web OTF

Buenas!

Esta entrada tendrá chicha, aviso...

Vamos a ver un pequeño editor de estilos OTF que creé para una web. Este editor está basado (lo que no quiere decir que ni por asomo sea igual de potente ni con las mismas características) en las herramientas de Google Chrome.

Sin duda, para el diseño web, estas herramientas van al dedillo, puesto que con ellas podemos modificar todo el diseño... pero al volver a cargar los cambios no están; con lo que tienes que hacer los cambios, plasmarlos en tu CSS/PHP (inline y direct), y subir los archivos otra vez.

Además, a veces también nos pasa que el estilo varia de un navegador a otro y/o de una resolución a otra... ¿verdad?

Pues me puse a ello y simplifiqué todo en una mini-herramienta que cambia el estilo, teniendo en cuenta:

  • Navegador
  • Versión (Solo para IE...)
  • Resolución de pantalla
Es decir, podemos modificar el estilo de IE7 (1280x800), y si volvemos a mirar con IE9 veremos que solo ha cambiado el estilo para el IE7; lo que permite hacer diseños casi exclusivos (en cuanto a que podemos hacer "fix" para cada navegador, versión, y resolución).

¿Qué necesitamos para hacer esto?
  • Dominio propio (recomiendo crear subdominio para desarrollo)
  • Entender JavaScript
  • Entender AJAX
  • Entender PHP
  • Entender OOP
Empecemos con un poco de teoría...

Yo suelo trabajar con subdominios específicos para desarrollo; de esta manera, la web "original" no caerá nunca. Este tema corre a cuenta de cada uno, ya que cada usuario tendrá su hosting y sus manías... jejeje

La teoría que veremos es casi toda de JavaScript; en concreto veremos el objeto "opener".

Opener es un objeto DOM que hace referencia (obviamente) al objeto window que ha abierto una ventana/popup/menú nuevo.

Es decir, supongamos esto:

<a href="miweb.com/pruebaOpener.html" target="_blank">Aquí abrimos un "child"</a>

La ventana nueva podrá "jugar" (de manera limitada, luego lo veremos) con opener, de manera que podría hacer, digamos:

<a href="#" onclick="opener.location.reload()">Recargar opener</a>

Y al pulsar el enlace, la página que abrió esta se recargaría. No obstante, como comentaba antes, el "juego" es limitado: por ejemplo, no podemos modificar estilos con "opener.getElementById(elemento).style..."

¿Entonces, cómo cambiaremos el diseño?

Fácil, nos tocará jugar con un elemento de opener que sí podemos controlar: "location".

Para jugar con el elemento location de opener, tendremos que usar varias veces el método "string.split", para  fraccionar la URI y componer una nueva (ya lo veremos en un poco).

Sobre AJAX, hay poco que explicar; aquí lo usaremos como mero puente entre JavaScript y una base de datos (unidos mediante una llamada a PHP). Yo recomiendo usar JavaScript puro, aunque se que muchos usáis jQuery y cosas así (no me gusta usar jQuery ni plug-in de este estilo... soy escéptico en cuanto a usarlos - ¡de ahí el nombre del blog!)...

Lo único "raro" que veremos de PHP son expresiones regulares. De ellas decir que sirven para evaluar formatos de cadena, y que son más o menos estándar en todos los lenguajes de programación que los utilizan (digo más o menos por no decir "exactamente iguales" y que luego no lo sean... jejeje)

Visto esto, ¡vamos allá!

Primero, crearemos una tabla en nuestra base de datos. La estructura que usaré yo en esta entrada corresponde a la siguiente tabla:

Tabla Style:

id - int - PK, NN, AI
class - varchar(45)
top - varchar(6)
left - varchar(6)
width - varchar(6)
height - varchar(6)
browser - int(2)
width_screen - int(4)
height_screen - int(4)

En esta tabla guardaremos los estilos propios de cada navegador y resolución.

Una vez creada la tabla en la base de datos, pasaremos a crear una serie de Class con PHP.

  • Parser.php (no es una clase exactamente)
  • Style.php
En el ejemplo, partiremos de la base de que estas clases están en la carpeta /Class.

Empezaremos con Style.php:

Primero creamos la estructura básica de la clase, con un __construct vacío.

Ahora veremos como creamos el menú para el estilo, en el método "menuLateral()"

$b = getBrowser($_SERVER['HTTP_USER_AGENT']);


return '<div style="position:relative;">
<form name="sstyle" action="#" method="post">
<span id="sclases">
<select size="20" name="clases" onchange="put();"></select>
</span>
<span id="sprop">
<br />
<hr />
Top: <input type="text" name="t" />
Left: <input type="text" name="l" />
Width: <input type="text" name="w" />
Height: <input type="text" name="h" />
</span>
<input type="button" name="s" value="Aplicar" onclick="applyChanges();" />
<input type="button" name="no" value="Reset" onclick="resetStyle();">
</form>
<div>
<br />
<hr />
Info:
<br />
Navegador: '.$b.' ('.$this->getBrowserFromNumber($b).')
<br />
<span id="res"></span>
</div>
</div>
<script type="text/javascript">fillInfo();fillForm();</script>';
En esta función, vemos una llamada a "getBrowser". Esta función tiene el código que expliqué en la entrada anterior (Aquí), a la que hemos añadido un simple else, dentro del bloque de navegadores no móviles (else{return 9;}).

También vemos un método llamado "getBrowserFromNumber(int)". Se detalla a continuación:

private function getBrowserFromNumber($n){
        $ret = array('Chrome', 'IE9', 'IE8', 'IE7', 'FireFox', 'Safari', 'Flock', 'Opera', 'NetScape', 'Otro');
  return $ret[$n];
}

Esta función nos devolverá el nombre genérico del navegador. Es la "inversa" de getBrowser.

Como veis, la función de menú no tiene gran cosa a explicar: creamos un select, cuatro inputs, dos buttons, y hacemos llamadas a JavaScript para llenar los datos necesarios.

Ahora pasaremos a hacer las funciones fillInfo() y fillForm(), en nuestro archivo JS.

function winSize(){
if(typeof window.innerWidth!='undefined'){
return[window.innerWidth,window.innerHeight]
}else if(typeof document.documentElement!='undefined'&&typeof document.documentElement.clientWidth!='undefined'&&document.documentElement.clientWidth!=0){
return[document.documentElement.clientWidth,document.documentElement.clientHeight]
}else{
return[document.getElementsByTagName('body')[0].clientWidth,document.getElementsByTagName('body')[0].clientHeight]
}
}

function fillInfo(){
s=winSize();
document.getElementById('res').innerHTML='Resolución de pantalla: Width('+s[0]+'px); Height('+s[1]+'px);'
}

function fillForm(){doAjax('./Class/Parser.php','f=1','sclases',0)}

Repasemos las funciones:

WinSize devuelve un array tal que:

array(width, height)

FillInfo edita el contenido del span "res" y le añade el alto y el ancho de pantalla.
FillForm llama, mediante AJAX, a la "clase" Parser, con los parámetros "f=1", indicando que el resultado irá en una sentencia "innerHTML"

** function doAjax(url, param, divToChange, param)
si param = 0 -> document.getElementById(divToChange).innerHTML = ...
si param = 1 -> eval(...) **

Para acabar esta parte, vamos a añadir algo a "Parser.php":

function splitCSS(){
$ret = '<select size="20" name="clases" onchange="put();">';
$f = fopen("../css/ct_style.css", "r");
$aux = explode("{", fread($f, filesize("../css/ct_style.css")));
fclose($f);
$a = array();
$a[] = $aux[0];

for($i=1;$i<count($aux);$i++){
$r = explode("}", $aux[$i]);

if(count(explode(".", $r[1])) == 2){
$a[] = trim($r[1]);
}
}

for($i=0;$i<count($a);$i++){
$ret .= '<option value="'.$a[$i].'">'.$a[$i].'</option>';
}

return $ret.'</select>';
}

Veamos que hace esta función:

Primero creamos el inicio de un select (correspondiente al del menú, que reemplazaremos por este).

Luego abrimos el archivo de estilos (css), lo leemos completamente y lo partimos en un array, tomando como limitador "{".

El siguiente paso es añadir la primera clase (que quedará suelta) en un array definitivo. Más tarde se itera sobre el array obtenido del archivo, y cada parte se fracciona por el caracter "}". Si al coger la segunda parte de esta fracción y partirla por "." obtenemos dos partes más, es que se trata de una clase CSS, y por tanto, la agregamos.

Finalmente, iteramos sobre el array final y creamos los options del select.

Lo he hecho con dos bucles para que quede algo más claro; si gustáis podéis concentrarlo todo en un solo for.

Tener en cuenta, sobretodo, que esta "clase" Parser no es una clase en si. Es más bien un archivo de funciones, colocado en "Class" por comodidad. Al inicio del archivo tenéis que crear un filtro, de manera que (por ahora)

  • Si $_POST['f'] = 1 -> splitCSS()
Ahora, tenemos listo el menú. Si accedemos a él nos cargará todas las clases CSS en un select.

Para acceder a la página del menú, podemos colocar un hipervínculo con target _blank, que llame a una página que cargue el menú.

Bueno, vamos ahora con la segunda parte, que es modificar los datos de nuestra base de datos (valga la redundancia). Esta parte tiene dos secciones:
  • Recuperamos datos existentes
  • Los modificamos
La recuperación de datos la haremos cada vez que el usuario pulse sobre un elemento del select (con el handler "put()"). Además, recargaremos el opener, para marcar mediante CSS el elemento que vamos a modificar.

function put(){
        s=winSize();
opener.location.href=getCurrentURL()+'&div='+document.sstyle.clases.options[document.sstyle.clases.selectedIndex].value.substring(1);
doAjax('./Class/Parser.php','f=2&e='+document.sstyle.clases.options[document.sstyle.clases.selectedIndex].value.substring(1)+'&w='+s[0]+'&h='+s[1],'sprop',0)
}

Veamos, esta clase primero obtiene las dimensiones de pantalla, luego recarga el opener con la url que ya tenía (tendréis que hacer esta función vosotros), con un parámetro GET adicional: "div". Ésta parte la veremos después.

Después llama mediante AJAX a Parser.php, con los siguientes datos POST:

  • f = 2
  • e = elemento seleccionado del select
  • w = width pantalla
  • h = height pantalla
Ahora tendréis que actualizar el filtro de Parser.php, para que en caso de f = 2, envíe a la función getCSS():

function getCSS($e, $w, $h){ $con = mysql_select_db("database", $connection = mysql_connect("host", "user", "pass")); $res = mysql_query("select `top`,`left`,`width`,`height` from ct_style where browser=".getBrowser($_SERVER['HTTP_USER_AGENT'])." and width_screen=".$w." and height_screen=".$h." and class='".$e."'"); $row = mysql_fetch_array($res, MYSQL_NUM); return '<br /><hr />Top: <input type="text" name="t" value="'.$row[0].'" />Left: <input type="text" name="l" value="'.$row[1].'" />Width: <input type="text" name="w" value="'.$row[2].'" />Height: <input type="text" name="h" value="'.$row[3].'" />'; }

Aquí recuperamos los datos de la clase (e) correspondientes al browser (getBrowser) y la resolución (w y h) actuales.
Posteriormente creamos de nuevo los inputs, con los datos correspondientes.

Ahora podemos modificar los datos en el input, y aplicarlos, o bien resetear el estilo a default.

Veremos ambas opciones. Primero veamos como se aplican los datos, mediante el handler "applyChanges()":

function applyChanges(){
s=winSize();
doAjax('./Class/Parser.php','f=3&c='+document.sstyle.clases.options[document.sstyle.clases.selectedIndex].value.substring(1)+'&t='+document.sstyle.t.value+'&l='+document.sstyle.l.value+'&w='+document.sstyle.w.value+'&h='+document.sstyle.h.value+'&ws='+s[0]+'&hs='+s[1],'sprop',0);
opener.location.reload()
}

Esta función llama a Parser.php (actualizad el filtro) con los siguientes parámetros:

  • f = 3
  • c = clase seleccionada en el select
  • t = top introducido
  • l = left introducido
  • w = width introducido
  • h = height introducido
  • ws = width screen
  • hs = height screen
Para actualizar los datos, usamos la función "putCSS":

function putCSS($c, $t, $l, $w, $h, $ws, $hs){
        $top = "";
$left = "";
$width = "";
$height = "";
$browser = getBrowser($_SERVER['HTTP_USER_AGENT']);
if(isset($t)){$top = $t;}
if(isset($l)){$left = $l;}
if(isset($w)){$width = $w;}
if(isset($h)){$height = $h;}
$con = mysql_select_db("database", $connection = mysql_connect("host", "user", "pass"));
        $res = mysql_query("select count(*) from ct_style where class='".$c."' and width_screen=".$ws." and height_screen=".$hs);
$row = mysql_fetch_array($res, MYSQL_NUM);
if($row[0] == 0){
mysql_query("insert into ct_style(`class`,`top`,`left`,`width`,`height`,`browser`,`width_screen`,`height_screen`) values('".$c."', '".$top."', '".$left."', '".$width."', '".$height."', ".$browser.", ".$ws.", ".$hs.")");
}else{
mysql_query("update ct_style set `top`='".$top."',`left`='".$left."',`width`='".$width."',`height`='".$height."' where `class`='".$c."' and `browser`=".$browser." and `width_screen`=".$ws." and `height_screen`=".$hs);
}
return '<br /><hr />Top: <input type="text" name="t" value="'.$t.'" />Left: <input type="text" name="l" value="'.$l.'" />Width: <input type="text" name="w" value="'.$w.'" />Height: <input type="text" name="h" value="'.$h.'" />';
}

La función de este trozo es sencilla: primero comprueba si los valores pasados por POST están vacíos o no; después comprueba si hay datos para ese navegador, ancho y alto.

Si hay datos, hará un update;sino, hará un insert.

Para teminar esta parte, veremos como se resetea el estilo, mediante el handler "resetStyle()":


function resetStyle(){ s=winSize(); doAjax('./Class/Parser.php', 'f=4&c='+document.sstyle.clases.options[document.sstyle.clases.selectedIndex].value.substring(1)+'&ws='+s[0]+'&hs='+s[1],'sprop',0); opener.location.reload() }

Aquí llamamos a Parser.php con los siguientes datos:

  • f = 4
  • c = clase seleccionada en el select
  • ws = width screen
  • hs = height screen
Debemos actualizar Parser.php para que el filtro nos lleve, en este caso, a popCSS:

function popCSS($c,$w,$h){

$con = mysql_select_db("database", $connection = mysql_connect("host", "user", "pass")); $res = mysql_query("delete from ct_style where `class`='".$c."' and `width_screen`=".$w." and `height_screen`=".$h." and `browser`=".getBrowser($_SERVER['HTTP_USER_AGENT']));
return '<br /><hr />Top: <input type="text" name="t" />Left: <input type="text" name="l" />Width: <input type="text" name="w" />Height: <input type="text" name="h" />';
}
Simplemente, capturamos la fila específica y la borramos, y actualizamos el form (con todo vacío).

Bien, ahora ya tenemos el sistema de actualizar/borrar (put/pop). Lo único que falta es modificar la web para que cargue un estilo específico.

Para ello haremos una llamada AJAX, que creará un CSS inline (<style></style>), y la rellenará con los datos correspondientes. Vamos allá

En el HTML plano, añadimos algo así:

<html>
  <head>
   ...
    <script type="text/css">style();</script>
  </head>
...

Esto llamará a la función style al cargar el HTML.

function style(){
s=winSize();
u=document.location.href.split("?");
x='';
u=u[1].split("&");

for(i=0;i<u.length;i++){
w=u[i].split("=");

if(w[0]=="div"){x='&x='+w[1]}
}

script=document.createElement("script");
script.setAttribute('src','./Class/Parser.php?f=1&w='+s[0]+'&h='+s[1]+x);
document.getElementsByTagName('head')[0].appendChild(script)
}

Ojo a la función, que tiene varios puntos importantes:

Primero obtenemos el tamaño (como siempre).

Luego partimos la URI por el caracter "?" (para obtener la URL y los parámetros por separado). El siguiente paso es dividir los parámetros en una lista, partiendo el segundo elemento de la lista anterior por el caracter ("&").

El siguiente paso es buscar si tiene el parámetro GET "div" (aquí es donde se conecta con la función "put()" de JS). Si lo tiene, lo añadiremos a nuestra ruta.

Una vez hecho esto, crearemos un elemento "script", con ruta en "./Class/Parser.php", con los siguientes parámetros GET (importante para el filtro):

  • f = 1
  • w = width screen
  • h = height screen
  • x = clase del elemento a marcar (si la hubiera)
Haremos ahora, que Parser, si GET['f'] = 1 nos lleve a loadStyle():

function loadCSS($w, $h, $x){
include_once "./Style.php";
$s = new Style();
return $s->getSpecificStyle($w, $h, $x);
}

Esta función simplemente delega en Style.php, en una función así:


public function getSpecificStyle($w, $h, $x){
$con = mysql_select_db("database", $connection = mysql_connect("host", "user", "pass"));
$res = mysql_query("select `class`,`top`,`left`,`width`,`height` from ct_style where browser='".getBrowser($_SERVER['HTTP_USER_AGENT'])."' and width_screen=".$w." and height_screen=".$h);
$str = '';

while($row = mysql_fetch_array($res, MYSQL_NUM)){
                        $str .= '.'.$row[0].'{top:'.$row[1].'px;left:'.$row[2].'px;width:'.$row[3].'px;height:'.$row[4].'px;}';
                }

if(isset($x)){
$str .= '.'.$x.'{background-color:#E31E26;border:1px solid #E31E26;}';
}

return 'css=document.createElement("style");css.innerHTML="'.$str.'";document.getElementsByTagName("head")[0].appendChild(css);';
}

Esta última función, retorna un contenido JS (que irá dentro del elemento "script" creado por "style"). Este contenido hace lo siguiente:

Busca en la base de datos todas las clases modificadas para ese navegador y resolución, y crea clases con el formato

.clase{top:Xpx;left:Xpx;width:Xpx;height:Xpx;}

El siguiente paso es mirar si hay un elemento seleccionado (esto sucede cuando cambiamos de selección el select de la página de estilos). Si lo hay, crearemos un estilo que lo marque en rojo, para ver qué elemento es.

La parte del elemento es transitoria, ya que cuando quitemos el parámetro GET "div", desaparecerá. Sin embargo, los datos de nuestra base de datos serán siempre estáticos.

Finalmente, en nuestro elemento "style" insertamos las clases CSS, y añadimos el elemento "style" a nuestro body.

Y.... ¡Voliá! Tenemos estilos por un tubo

Y aquí concluye esta paranoica y escéptica función de diseño creada 100% por mi.

En las próximas entradas, contaré el curioso caso de cómo con un pequeño descuido se puede comprometer toda una web...