Curso de Brython en Español - Crear etiquetas html dinámicamente (browser.html)


Crear etiquetas html desde Brython

En las secciones anteriores hemos visto como interáctuar con las etiquetas html de la página. En esta sección vamos a explorar el módulo browser.html de brython, que nos permite crear elementos html y añadirlos al documento.

Crea un archivo index.html como el del primer capítulo. Ejecuta un servidor local con python -m http.server y abre la dirección http://localhost:8000.

El código python irá en el interior de la etiqueta script de tipo text/python. Si tienes dudas visita los capítulos anteriores del curso.

Veámos un ejemplo:

from browser import document
from browser.html import *

document <= H1('Título creado con Brython')

En este ejemplo hemos creado una etiqueta h1 y la hemos añadido al document. En realidades la hemos añadido a body, y el siguiente código funcionaría exactamente igual:

from browser import document
from browser.html import *

document.body <= H1('Título creado con Brython')

El módulo browser.html dispone de clases para crear cualquier etiqueta html. Estas clases siempre están identificadas como el nombre de la etiqueta en mayúsculas. Si quieres crear una etiqueta h1 utiliza H1, si quieres crear una etiqueta p utiliza P, etc.

Por ejemplo, en nuestro documento vacío vamos a añadir un título, un subtítulo y un par de párrafos:

from browser import document
from browser.html import *

document <= H1('Título de la página')
document <= H2('Subtítulo de la página')
document <= P('Soy el primer párrafo del documento.')
document <= P('<strong>Soy un texto en negrita en el segundo párrafo del documento.</strong>')

El operador binario <=, tomando dos clases definidas en browser.html como operandos, se comporta como el método appendChild de javascript. Obtendrías el mismo resultado con este código:

from browser import document
from browser.html import *

document.body.appendChild(H1('Título de la página'))
document.body.appendChild(H2('Subtítulo de la página'))
document.body.appendChild(P('Soy el primer párrafo del documento.'))
document.body.appendChild(P('<strong>Soy un texto en negrita en el segundo párrafo del documento.</strong>'))

Utilizar <= queda mucho mejor, es mas pythonico. Además, puesto que document solamente puede contener un elemento, y siempre que se quieren agregar elementos al documento tienes que incluirse en document.body, en brython se permite operar directamente sobre document, añadiendo los elementos creados a document.body implícitamente.

Fíjate en el segundo párrafo, que se mostrará en negrita. Puedes conseguir el mismo resultado utilizando STRONG:

from browser import document
from browser.html import *

document <= H1('Título de la página')
document <= H2('Subtítulo de la página')
document <= P('Soy el primer párrafo del documento.')
document <= P(STRONG('Soy un texto en negrita en el segundo párrafo del documento.'))

Pero además, cualquier clase en browser.html puede recibir una lista de elementos como primer parámetro en lugar de una cadena de texto o un elemento individual:

from browser import document
from browser.html import *

document <= H1('Título de la página')
document <= H2('Subtítulo de la página')
document <= P('Soy el primer párrafo del documento.')

document <= P([

    SPAN('Soy el primer párrafo. '),
    STRONG('Esto está escrito en negrita. '),
    EM('Y esto está escrito en cursiva')

    ])

Esto puede ser muy útil, por ejemplo, para crear listas:

from browser import document
from browser.html import *

document <= OL([

    LI('Primer elemento'),
    LI('Segundo elemento'),
    LI('Tercer elemento')

    ])

Podrías utilizar listas de compresión:

from browser import document
from browser.html import *

document <= OL([LI(f'Elemento {i+1}') for i in range(10)])



Estilo de las etiquetas

Las clases definidas en browser.html reciben un número arbitrario de keyword-arguments para definir los atributos de la etiqueta que se va a crear. Por ejemplo:

from browser import document
from browser.html import *

parrafo = P('Hola, yo me llamo Ralf', className='parrafo')

document <= parrafo

Si no has definido estilos en tu documento html, y no has enlazado una hoja de estilos, puedes añadir una etiqueta style así:

from browser import document
from browser.html import *

document <= STYLE('''

.parrafo {
    color: blue;
    background-color: yellow;
}

''')

parrafo = P('Hola, yo me llamo Ralf', Class='parrafo')
document <= parrafo

Hemos añadido una etiqueta style en la que definimos el estilo para todos los elementos cuyo atributo class es igual a parrafo. Fíjate que en P('Hola, yo me llamo Ralf', Class='parrafo') utilizamos Class en vez de class. Esto se debe a que class es una palabra reservada de python.

Pero no es necesario crear una etiqueta style. Por ejemplo:

from browser import document
from browser.html import *

document <= P('Hola, yo me llamo Ralf', style=dict(color='yellow', backgroundColor='red'))

Ahora vamos a crear un script que añade varios párrafos al documento, alternando su estilo:

from browser import document
from browser.html import *

estilos_1 = dict(color='red', backgroundColor='blue')
estilos_2 = dict(color='blue', backgroundColor='red')

for i in range(10):

    if i % 2 == 0:
    	estilos = estilos_1
    else:
    	estilos = estilos_2

    document <= P(f'Párrafo {i+1}', style=estilos)

También puedes modificar el estilo de un elemento una vez creado y añadido al documento. Obtendrías el mismo resultado así:

from browser import document
from browser.html import *

parrafos = [P(f'Párrafo {i+1}') for i in range(10)]
document <= parrafos

for p in parrafos:

    if parrafos.index(p) % 2 == 0:
    	p.style['color'] = 'red'
    	p.style['backgroundColor'] = 'blue'
    else:
    	p.style['color'] = 'blue'
    	p.style['backgroundColor'] = 'red'

Simplemente hemos asignado un nuevo valor a las claves del diccionario styles.

Quizás te sorprenda que lo siguiente también funciona:

from browser import document
from browser.html import *

parrafos = [P(f'Párrafo {i+1}') for i in range(10)]
document <= parrafos

for p in parrafos:

    if parrafos.index(p) % 2 == 0:
    	p.style.color = 'red'
    	p.style.backgroundColor = 'blue'
    else:
    	p.style.color = 'blue'
    	p.style.backgroundColor = 'red'

¿Cómo es posible? En python no puede accederse a las claves de un diccionario como si fuesen atributos de un objeto, aunque podrías crear una clase personalizada e implementar este funcionamiento.

Lo que sucede es que brython es una implementación de python escrita en javascript, y los diccionarios de brython se convierten automáticamente en objetos de javascript cuando es necesario. El valor del atributo style de un elemento html debe ser un objeto javascript, de manera que si asignamos un diccionario, este es convertido.

Observa la sentencia document <= parrafos: El operando a la derecha de <= puede ser una lista de elementos. También podría ser la suma de varios elementos, concatenados con +:

from browser import document
from browser.html import *

parrafos = P('Primer párrafo') + P('Segundo párrafo') + P('Tercer párrafo')
document <= parrafos

Como puedes observar, en en el desarrollo del módulo browser.html de brython se ha hecho un uso extenso de la sobrecarga de operadores.


Crear una tabla html

Para finalizar y resumir, vamos a crear una tabla html para demostrar lo bien creado que está el módulo browser.html. Iremos optimizando el modo en el que resolvemos el problema:

from browser import document
from browser.html import *

document <= STYLE('''

td {
    border: 1px solid black;
    padding: 1em;
}

''')

tabla = TABLE()
document <= tabla

# FILA 1
fila_1 = TR()
tabla <= fila_1

elemento_1 = TD('Me')
elemento_2 = TD('gusta')
fila_1 <= elemento_1 + elemento_2

# FILA 2
fila_2 = TR()
tabla <= fila_2

elemento_1 = TD('mucho')
elemento_2 = TD('Brython')
fila_2 <= elemento_1 + elemento_2

Esto está bien y funciona perfectamente, pero puedes hacer lo mismo escribiendo mucho menos:

from browser import document
from browser.html import *

document <= STYLE('''

td {
    border: 1px solid black;
    padding: 1em;
}

''')

# FILA 1
fila_1 = TR()
fila_1 <= TD('Me') + TD('gusta')

# FILA 2
fila_2 = TR()
fila_2 <= TD('mucho') + TD('Brython')

document <= TABLE(fila_1 + fila_2)

Lo mismo puede conseguirse utilizando listas en lugar de concatenando elementos con +:

from browser import document
from browser.html import *

document <= STYLE('td{border: 1px solid black;padding: 1em;}')

# FILA 1
fila_1 = TR()
fila_1 <= [TD('Me'), TD('gusta')]

# FILA 2
fila_2 = TR()
fila_2 <= [TD('mucho'), TD('Brython')]

document <= TABLE(fila_1 + fila_2)

Puedes simplificar incluso mas:

from browser import document
from browser.html import *

document <= STYLE('td{border: 1px solid black;padding: 1em;}')

fila_1 = TR([TD('Me'), TD('gusta')])
fila_2 = TR([TD('mucho'), TD('Brython')])

document <= TABLE(fila_1 + fila_2)

Y finalmente, la manera mas compacta de crear una tabla en brython:

from browser import document
from browser.html import *

document <= STYLE('td{border: 1px solid black;padding: 1em;}')

document <= TABLE([
    TR([TD('Me'), TD('gusta')]),
    TR([TD('mucho'), TD('Brython')])
    ])

Bueno, si te parece muy lioso el uso de listas, concatena!!

from browser import document
from browser.html import *

document <= STYLE('td{border: 1px solid black;padding: 1em;}')

document <= TABLE(
    TR(TD('Me') + TD('gusta')) +
    TR(TD('mucho') + TD('Brython'))
    )

Puedes usar listas o concatenar con + los elementos. Esto es así porque concatenando se consiguie un código mas limpio, pero muy frecuentemente crearás listas de elementos en tus aplicaciones brython, por lo que viene muy bien poder añadirlas directamente a otros elementos.

Si no te han convencido las listas, y crees que siempre vas a utilizar la concatenación con + para añadir elementos, observa el resultado de este último ejemplo:

from browser import document
from browser.html import *

document <= STYLE('td{padding:1em;}')

tabla = TABLE()

for i in range(10):
    tabla <= TR([TD(f'F{i}-C{j}') for j in range(10)])

for f_index, f in enumerate(tabla):
    for c_index, c in enumerate(f):
    	if (f_index % 2 and c_index % 2) or (not f_index % 2 and not c_index % 2):
    		c.style.backgroundColor = 'gray'


document <= tabla

En este caso he utilizado un lista de compresión para crear las celdas que contiene cada fila.

Fíjate en que he utilizado enumerate(tabla) y enumerate(f), siendo tabla y f DOMElements. No son listas de python, pero hemos iterado sobre estos elementos como si lo fuesen (obteniendo el índice en cada iteración con enumerate). Esta capacidad resulta muy conveniente, y es el motivo por el que en brython se ha optado por permitir la iteración directa sobre los hijos de un elemento html, aunque puedes utilizar explícitamente tabla.children o f.children.