Validación de HTML y dojo


Antes que nada, necesito agrandar mi lista de software favorito :), Enrique y Alejandro fueron tan amables de hacerme ver que esta extremadamente corta en cuestión de herramientas importantes, espero hacerlo mañana !

Y entrando en tema, la validación de HTML es algo que muchos valoramos !! (valga la rebusnancia cacofónica), en el principio de los días WWW-eros se creó un lenguaje para crear la forma de comunicación más importante que tenemos actualmente, la web; este lenguaje implementó los elementos necesarios para hacer tareas tales como insertar texto, títulos, columnas, imágenes, y todo tipo de material multimedia.

Ese lenguaje era bastante burdo, pero fué mejorando relativamente rápido, uno de los grandes pasos fué la inclusión de las hojas de estilo (CSS, Cascade style sheets) para separar vista de presentación, recuerdo la famosa frase de que “el HTML violaba la definición de un documento”, o algo así.

Se empezaron a crear definiciones cada vez mas formales y robustas de lo que ahora conocemos con HTML y el nuevo y mejor XHTML, no solo tenemos definiciones muy formales de como representar los datos de una página y su estilo (HTML y CSS respectivamente), sino que ahora tenemos incluso un gran estandar de comunicacion e interacción hacia lo que se conoce como DOM (Document Object Model) a través de otro lenguaje más, el JavaScript (que dijeron ?? ActionScript ?? pues no, pero se parecen y son como hermanos).

Pero llegó la era 2.0 de la Web !!, ahora no nos conformamos con páginas estáticas, ni siquiera con páginas dinámicas en contenido, ahora queremos que las “aplicaciones web” sean amigables, colaborativas, mashupeables (sin albur), etc.

Que pasa, que o usamos flash para hacer aplicaciones contenidas en un archivo SWF (con herramientas propietarias, y todo lo malo que implica eso), o usar JavaScript y todo su poder para agregarle a las paginitas algo de color, belleza, y movimiento, sin dejar atras usabilidad, rapidez y flexibilidad.

Creo que es la introducción más larga a un blogaso que he escrito en mi vida.

Bueno, siguiendo, obviamente en cuanto apareció la dichosa nueva era del Web 2.0, aparecieron muchas herramientas (bibliotecas) para facilitar el trabajo de crear las páginas bajo la nueva tendencia.

Uno de los más populares, probablemente no el más, es Dojo, que incluye todo lo necesario para hacer AJAX, widgets, layouts, effects, 😛 ya me pasé con el spanglish.

El asunto con Dojo es que graciosamente deciden crearse todo un sistema de creación de efectos y herramientas visuales y funcionales que depende nada más ni nada menos que de la inclusión de un atributo a los tags de HTML, en concreto “dojoType”. Es una forma muy sencilla de crear toda clase de cosas de manera sencilla (el parser de dojo hace el trabajo de localizar los elementos que debe transformar) pero automáticamente invalida no solo XHTML, sino tambien cualquier definición oficial de HTML.

Pero ese no es el fin del mundo, es totalmente posible crear páginas validas (en cuanto a [X]HTML) pero implica un pequeño detalle, hay que programar la creación de todos los elementos !!, ahhhhh, ahora resulta que si tengo que saber JavaScript para hacer una página correcta.

En realidad no es demasiado tedioso hacer la creación de elementos con programación, sobre todo si nos importa mucho crear páginas que sean válidas bajo los estándares de web, pero es un hecho que es más latoso que simplemente irte por la vía rápida y crear todo en el sencillo HTML.

Veamos un ejemplo sencillo, vamos a crear una página con una sola sección que va a contener una tabla tipo “Grid” con todas las bondades que tiene, obviamente le vamos a meter datos, los datos vendrán de un archivo que devuelve json (al estilo django, de ahí lo saqué) que puede representar de manera sencilla la salida de cualquier consulta a una base de datos a través de django (cualquier otro método requiere modificaciones muy sencillas).

Si tienes git, puedes correr “git clone http://garaged.homeip.net/samples/dojosample.git” para bajar el ejemplo, hay que sustituir el link en “my_media/dojo” por un directorio que tenga dojo descomprimido (debe contener dojo,dijit, dojox, util), o puedes bajar el zip.

Usando los atributos de dojo (extrañosa a HTML) podemos simplemente crear un layout directamente en el body:


<body class="tundra">

<div dojoType="dijit.layout.BorderContainer" design="headline"
style="border: 2px solid black; width: 90%; height: 90%; padding: 10px; margin: auto auto auto auto; vertical-align: center;" >
<div dojoType="dijit.layout.ContentPane" region="center" style="width: 100%; height: 100%">
<div id="grid" dojoType="dojox.Grid" structure="layout" title="Tab 2"></div>
</div>
</div>

</body>

Dentro del contenedor ya estamos definiendo un “Grid”, que va a tomar su formato de celdas de un arreglo que se llama “layout”, eso lo definimos en un archivo con JavaScript que llamamos en los headers (grid.js).


var layout = [ {
cells: [[
{name: 'Id'}, {name: 'From'}, {name: 'To'}, {name: 'Spam?'}, {name: 'Score'}
]]
}];

Con ello tenemos ya definido un esquema en el que existe un contenedor principal, que tiene un “hijo” que aloja un “grid” el cual tiene 5 columnas, los datos se cargan con el resto del JavaScript en “grid.js”.


dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("dojox.grid.Grid");
dojo.require("dojo.parser"); // scan page for widgets and instantiate them

var data_json = new Array();
function getData(url){
dojo.xhrGet({
url: url,
handleAs: "json",
load: makeGrid // smart function call !
});
}

dojo.addOnLoad(function(){
getData("./data.json");
});

function makeGrid(data, ioArgs) {
var i;
for (i=0 ; i<data.length-1; i++ ) {
data_json[i] = [
data[i].pk,
data[i].fields.from_add,
data[i].fields.to_add,
data[i].fields.spam,
data[i].fields.score
]
}
var Model = new dojox.grid.data.Objects(null, data_json);
dijit.byId("grid").setModel(Model);
}

que entre otras cosas hace lo necesario para que se cargue dojo, y se haga el "parseo" del HTML para cambiar todos los tags que tengan un "dojoType" por el correspondiente "widget". Luego por medio de "dojo.addOnLoad" desencadenamos el proceso que trae el archivo data.json y con un tratamiento nada cesudo se convierte en datos que son procesados para ser incluidos en el "Grid".

En el archivo index.html completo puedes ver que tambien se cargan varios archivos CSS que son indispensables para general la tabla en el formato correcto, y muchoas otras monadas que se hacen por debajo del agua automáticamente.

Este ejemplo fué hecho rompiendo el estandar de HTML, insertamos tags que tienen el atributo "dojoType", el cual no es aceptado, y por tanto la página no es válida segun el estandar.

Ahora veamos un diff de la página creando todo "programaticamente" con JavaScript.


--- index.html 2008-05-29 17:26:00.000000000 -0500
+++ index-programatic.html 2008-05-29 17:25:31.000000000 -0500
@@ -9,23 +9,20 @@
@import "./my_media/dojo/dojo/resources/dojo.css";
@import "./my_media/dojo/dijit/themes/tundra/tundra.css";
html, body { height: 100%; width: 100%; margin: 0; padding: 0; }
+ #grid { width: 100%; height: 100%; }
+ #container { border: 2px solid black; width: 90%; height: 90%; margin: auto auto auto auto; vertical-align: center;}
</style>

<script type="text/javascript" src="./my_media/dojo/dojo/dojo.js"
- djConfig="parseOnLoad: true" ></script>
- <script type="text/javascript" src="./my_media/js/grid.js"></script>
+ >
+ </script>
+ <script type="text/javascript" src="./my_media/js/grid-programatic.js"></script>



</head>
-<body class="tundra">
+<body>

-<div dojoType="dijit.layout.BorderContainer" design="headline"
- style="border: 2px solid black; width: 90%; height: 90%; padding: 10px; margin: auto auto auto auto; vertical-align: center;" >
- <div dojoType="dijit.layout.ContentPane" region="center" style="width: 100%; height: 100%">
- <div id="grid" dojoType="dojox.Grid" structure="layout" title="Tab 2"></div>
- </div>
-</div>

</body>
</html>

Notablemente ya no necesitamos el "parseOnLoad" y además el body quedó vacío!

Es porque vamos a hacer todo con JavaScript (mmm, sin palabras). Veamos el diff para grid.js

--- grid.js      2008-05-29 16:55:02.000000000 -0500                                       
+++ grid-programatic.js 2008-05-29 16:54:15.000000000 -0500
@@ -1,7 +1,5 @@
dojo.require("dijit.layout.BorderContainer");
-dojo.require("dijit.layout.ContentPane");
dojo.require("dojox.grid.Grid");
-dojo.require("dojo.parser"); // scan page for widgets and instantiate them

var data_json = new Array();
function getData(url){
@@ -17,6 +15,11 @@
});

function makeGrid(data, ioArgs) {
+ var layout = [ {
+cells: [[
+ {name: 'Id'}, {name: 'From'}, {name: 'To'}, {name: 'Spam?'}, {name: 'Score'}
+ ]]
+ }];
var i;
for (i=0 ; i<data.length-1; i++ ) {
data_json[i] = [
@@ -27,13 +30,23 @@
data[i].fields.score
]
}
- var Model = new dojox.grid.data.Objects(null, data_json);
- dijit.byId("grid").setModel(Model);
+ var cont = new dijit.layout.BorderContainer(
+ { "design": "headline",
+ "id": "container"
+ }
+ );
+ document.body.appendChild(cont.domNode);
+ var newGrid = new dojox.Grid(
+ { "id": "grid",
+ "structure": layout,
+ "region": "center"
+ }
+ );
+ dijit.byId("container").addChild(newGrid);
+ Model = new dojox.grid.data.Table(null, data_json);
+ dijit.byId('grid').setModel(Model);
+ newGrid.render();
+
}

-var layout = [ {
-cells: [[
- {name: 'Id'}, {name: 'From'}, {name: 'To'}, {name: 'Spam?'}, {name: 'Score'}
- ]]
-}];

Ahora el arreglo "layout" que define las columnas del "Grid" está en la funcion que lo hace, ya no necesitamos que sea accesible desde fuera de esta función.

Secuencialmente en la función "makeGrid" creamos un contenedor (ya no se hace en HTML), y le insertamos un hijo que va a ser el Grid, luego alimentamos ese Grid con los datos que traemos por medio de AJAX (por si no se notó) y finalmente llamamos al "render" de la tabla.

Con estos cambios, que aunque no son demasiado complicados, si requieren de más cesos, principalmente porque NO HAY DOCUMENTACIÓN de como hacer BorderContainer programacionalmente todavía en dojo, así que uno se tiene que chutar amablemente el código en JavaScript y entenderlo lo suficiente como para aprender a hacer lo que hace el parseador de los BorderContainer en dojo.

Con esto doy por terminado el día, y como nota hay que poner un

en el body para que valide, aparentemente un body vacío no es válido !! aún así no se logra validar 100% !! jajajaja

Cabe mencionar que herramientas como scriptaculous y meteora hacen todo programáticamente, por lo que en una de esas es preferible usarlas, pero a mi me gusta dojo 😛



django: Todo hecho fácil


Django es la neta del planeta para mi 🙂

Lo conocí gracias a un comentario del “buena voz mother fucker” hace unos meses, y como python es mi lenguaje favorito cuando se trata de aprender algo nuevo, pues inmediatamente me puse a checar ese nuevo “framework” y en cuestión de horas estaba totalmente enamorado :P.

Cuando se trata de programar en Python, simplemente no soy bueno, durante un par de años he hecho cosas sencillas, incluso con Glade y GTK, y recuerdo cuando estaba en tmz que me avente un “servidor de chat” muy simple, que tiempos aquellos, pero simplemente no soy muy productivo escribiendo Python, así que a veces me toma muchas horas hacer algo sencillo, de hecho luego sacando cuentas el numero de lineas por hora que escribo llega a ser muy cercano a 1 !! pero siempre quedo contento porque a pesar de terminar con muy pocas lineas, siempre logro tener algo que, al menos para mi, se ve muy elegante, y hace exactamente lo que quiero.

Y volviendo a Django, simplemente es asombroso lo que se puede hacer con tan poco conocimiento, voy a exponer unas cuantas lineas de un sistema pqueño que estoy haciendo para guardar datos de usuarios en la red, es realmente simple, pero preferí hacerlo en Python/Django que en PHP que es mi lenguaje más productivo en general (para web casi exclusivamente), y la razón de la elección fué que Django me permite hacer trivialmente varias tareas: autenticación, localización (traducción), separación MVC (al estilo Django, pero muy elegante), portabilidad en la base de datos, uso de plantillas, URL bonitos, y como dicen en su sitio, “no me repito”, o sea, las cosas se hacen de la mejor manera para que sea trivial reproducirlas cuantas veces sea necesario sin mucho código (one-liners seria la “palabra correcta”), y además de todo me crea un panel de control en el que puedo editar datos a placer.

Como antecedente, me tomo unas 4 a 6 horas hacer lo que voy a escribir aqui, y este es el resumen de programación:
Lineas Archivo (wc -l)
10 urls.py
37 base/models.py
8 base/urls.py
40 base/views.py
95 total

en las plantillas tengo mas de 130 lineas, pero pues ni vale la pena contar eso porque el gaste de HTML puede ser arbitrario, depende de cuando diseño le quieras meter, y que tan hábil seas para hacerlo de manera elegante.

En realidad esta no es la aplicación terminada, solo hice un ejemplo que necesito para reproducirlo para todas las tablas que sean necesarias, este ejemplo es un listado paginado, con soporte de todo lo que dije arriba.

No quiero irme sobre todos los detalles de la configuración (settings.py) de la aplicación, simplemente voy a remitirme a la parte del listado paginado.

en una “sub aplicación” de la aplicación principal tengo este url.py:


from django.conf.urls.defaults import *
from iplist.base import models, views

urlpatterns = patterns('',
(r'^$', 'iplist.base.views.myview'),
(r'^view/$', views.object_list, {'model': models.People}),
)

Lo que hace es definir 2 listados parecidos, el segundo por medio de las vistas genéricas que proporciona Django (generic.views), ambos paginan, pero el segundo es realmente elegante y portable, mientras que el primero es una vista a una sola tabla en particular.

Este es el archivo view.py, que contiene las 2 vistas en cuestion:


from django.template import Context, loader
from django.contrib.auth.decorators import login_required
from iplist.base.models import People
from django.core.paginator import ObjectPaginator, InvalidPage

from django.http import HttpResponse, HttpResponseRedirect
from django.views.generic import list_detail

@login_required
def myview(request):
people_list = ObjectPaginator(People.objects.all().order_by('name'), 3)
t = loader.get_template('base/list.html')
try:
page = int(request.GET.get('page'));
except:
page = 0
print page, people_list.pages
if (page < people_list.pages ):
c = Context({
'people_list': people_list.get_page(page),
'page': page,
'has_next': people_list.has_next_page(page),
'has_prev': people_list.has_previous_page(page),
'next_page': page+1 ,
'prev_page': page-1 ,
'paginator': people_list,
})
return HttpResponse(t.render(c))
else:
return HttpResponseRedirect('/list/')

@login_required
def object_list(request, model):
try:
return list_detail.object_list(
request,
queryset = model.objects.all().order_by('name'),
template_name = 'base/%s_list.html' % model.__name__.lower(),
paginate_by = 3,
)
except:
return HttpResponseRedirect('/list/view')

Las primeras lineas de “import” son necesarias para la vista de la tabla People paginada, los ultimos 2 “import”s son los que se necesitan para la vista genérica, de hecho el último contiene 2 funciones necesaria para la primera vista, y otra para la segunda, y la linea que importa login_required se usa en los 2 (5 contra 3 imports!).

En la primera vista usamos la herramienta de paginación de Django, y seleccionamos una tabla sobre la cual vamos a iterar en la plantilla, después tenemos que tomar la plantilla y alimentarla con los datos necesarios. Aquí un detalle importante, las plantillas no pueden accesar a métodos, solo accesan valores, arreglos, etc, por lo que es necesario alimentar bastante, en ves de por ejemplo pasar el objeto people_list con todas sus capacidades.

La segunda vista/clase/funcion/como-quieras-llamarle toma el parametro “page” automáticamente de GET, pagina, y manda los parámetros relevantes a la plantilla (pagina proxima, numero total de paginas, de registros, etc) con solo agregar “paginate_by” a la funcion list_detail.object_list.

NOTA: la linea precediendo a cada función hace necesario estar logueado con un usuario para ver la vista, las tablas y sistema de autenticacion los creo Django por mi, si quieres tener una forma de logueo solo creas una plantilla básica y agregas una linea a url.py y listo ! todo lo demás Django lo hace sin ayuda 🙂

De tal manera que esta plantilla (templates/base/people_list.html) hace todo lo que se esperaría de una lista paginada:


{% load i18n %}
{% extends "base.html" %}

{% block title %}{% trans "Generic List" %}{% endblock %}

{% block content %}
{% for entry in object_list %}
<div style="border:1px solid black;">
<h1>{{ entry.name }}</h1>
<p>{{ entry.comments }} ({{ entry.department }})</p>
</div>
{% endfor %}
{% if has_previous %}
<a href="?page={{ previous }}">&lt;</a>
{% else %}
{% trans "No Previous Page" %}
{% endif %}
|
{% if has_next %}
<a href="?page={{ next }}">&gt;</a>
{% else %}
{%trans "No More Pages" %}
{% endif %}
<br />
{% endblock %}

a través del segundo patrón en el url.py que puse arriba, y si notan todos los textos están marcados para traducción (notar la primera linea, es importante que sea así), pero para no hacer el cuento muy largo eso lo dejo para otro post (si es que lo llego a hacer :P).

Y dejo aquí la plantilla de la vista “no genérica”:


{% load i18n %}

{% extends "base.html" %}

{% block title %}{% trans "Another Testing Template" %}{% endblock %}

{% block content %}
{% for entry in people_list %}
<div style="border='1px';">
<h2>{{ entry.name }}</h2>
<p>{{ entry.comments }}</p>
</div>
{% endfor %}
{% if has_prev %}
<a href="?page={{ prev_page }}">&lt;</a>
{% else %}
{% trans "No Previous Page" %}
{% endif %}
|
{% if has_next %}
<a href="?page={{ next_page }}">&gt;</a>
{% else %}
{%trans "No More Pages" %}
{% endif %}
{% endblock %}

En conclusión, podemos lograr en 11 lineas de programación tener una función que nos pagina, y protege con autenticación el listado de cualquier tabla, ya que el nombre de la tabla lo mandamos a través de un parámetro en una linea de url.py, de tal manera que podemos hacer cuantos querramos y reutilizar la misma vista, y al final solo tenemos que crear plantillas personalizadas para cada vista, ya que cada tabla tienen campos diferentes, o no ?? 🙂

O en otras palabras, copias una linea en url.py, le cambias lo esencial, y copias la plantilla y haces lo mismo, igual y quieres un diseño totalmente diferente para cada listado :), y es todo, volvemos aún mas aburrido el trabajo del desarrollador web ! y le ahorramos un buen de tiempo, ni siquiera tiene que aprender struts !!

Hasta la proxima

P.D. Notaron la elegancia de la validación para page ?? es o no bonito Python ??