Tutorial Compojure II: Generando vistas con Hiccup

En el post anterior de este tutorial creamos la estructura del proyecto web para compojure y vimos los conceptos básicos necesarios para empezar a trabajar. Ha llegado el momento de entrar en materia y comenzar a construir la aplicación.

La aplicación

En este tutorial vamos a desarrollar una aplicación web sencilla, pero que nos va a permitir tocar varios puntos habituales en este tipo de aplicaciones: servir contenido estático, generar html dinámicamente y exponer un API accesible desde Javascript.

Como se me da bastante mal lo de buscar aplicaciones de ejemplo, vamos a reimplementar la misma aplicación que usamos en el tutorial de node.js + express + jquery: MegaFacts.

La idea es muy simple, crearemos una aplicación que nos permita manejar listas de facts, al estilo de los Chuck Norris Facts, Bruce Schneier Facts o Pérez-Reverte Facts. El aspecto de nuestra página principal (y única, ya que se tratará de una SPA), será algo similar a esto:

Mockup de la página de Mega Facts

En la parte izquierda mostraremos una lista de personas de las que conocemos facts. Al pulsar sobre una de ellas mostraremos en la parte central la lista de facts conocidos, permitiendo añadir nuevos facts.

Sirviendo contenido estático

En casi todas las aplicaciones es necesario servir contenido estático de una forma u otra. Podemos servir contenido estático desde una aplicación compojure creando una ruta usando la macro files. Para ello modificaremos nuestra definición de rutas (recordad que está en el fichero ./src/compojure_sample/handler.clj):

(defroutes app-routes
  (GET "/" [] "Hello World")
  (route/files "/public" {:root "public"})
  (route/not-found "Not Found"))

Los parámetros de la macro files son la ruta física en disco donde se almacena el contenido estático y un map en el que podemos configurar varios aspectos, entre ellos la ruta lógica que se usará para acceder a los recursos.

Es importante tener en cuenta que la ruta física indicada a la macro files es relativa a la raíz del proyecto, es decir, a la ubicación en que se encuentra el fichero project.clj.

Usando Hiccup para crear páginas dinámicamente

En clojure existen unos cuantos sistemas de plantillas para generar páginas HTML y todos ellos son bastante fáciles de user con compojure, pero seguramente el más popular sea hiccup.

Para poder usar hiccup deberemos incluirlo como dependencia de nuestro proyecto en el fichero project.clj:

(defproject compojure-sample "0.1.0-SNAPSHOT"
  :description "Sample compojure project"
  :url "https://blog.koalite.com/2013/03/tutorial-compojure-i-creando-una-aplicacion-web-funcional/"
  :dependencies [[org.clojure/clojure "1.4.0"]
                 [compojure "1.1.5"]
                 ; Añadimos la dependencia de hiccup versión 1.0.2
                 [hiccup "1.0.2"]]
  :plugins [[lein-ring "0.8.2"]]
  :ring {:handler compojure-sample.handler/app}
  :profiles
  {:dev {:dependencies [[ring-mock "0.1.3"]]}})

Un aspecto interesante de hiccup para los que venimos de otro tipo de lenguajes es que en lugar de utilizar una sintaxis específica para la generación de HTML (Razor, Jade, Haml, etc.), se utiliza directamente código clojure (lo más parecido que he visto en un lenguaje estático es en Kotlin).

Todo ello además sin necesidad de definir estructuras de datos complejas, sino aprovechando las estructuras básicas del lenguaje, como comentaba hace no mucho al hablar de estructuras de datos sin esquema.

En hiccup cada elemento HTML se representa como un vector con el siguiente aspecto:

[:elemento#id.class {:attr1 val1 :attr2 val2} contenido]

El primer elemento es el elemento HTML propiamente dicho, además podemos usar selectores css para añadirle un id y una o más clases. A continuación podemos utilizar un map para definir atributos del elementos y por último indicaremos el contenido del elemento, que puede ser texto o más elementos anidados.

Para crear nuestra página principal (y única), añadiremos el fichero ./src/compojure_sample/views.clj con el siguiente contenido:

(ns compojure-sample.views
  (:use hiccup.page)
  (:use hiccup.element))
			
(defn index [hero-names]
  (html5 
  
    [:head
	  (include-css "/public/css/style.css")
	  (include-js "/public/js/jquery-1.9.1.min.css")
	  [:title "Mega Facts"]]
	  
	[:body
	  [:div#container
	  
	    [:div#header
		  [:h1 "Mega Facts"]]
		  
		[:div#content
		  [:div#left-column.column
		    [:h2 "Elige tu héroe"]
			[:ul#heroes
			  (for [name hero-names]
			    [:li.hero-name (link-to "#" name)])]]
		  [:div#right-column.column
		    [:h2 "Facts"]
			[:ul#facts]
			[:label {:for "new-fact"} "Añadir nuevo fact:"]
			[:textarea#new-fact]
			[:a#add-new-fact {:href "#"} "Añadir"]]]
			
		[:div#footer
		  [:a {:href "https://blog.koalite.com"}]]]]))

En este fichero la función index dentro del espacio de nombres y compojure-sample.views e incluimos en ella la generación del HTML. Además de las etiquetas se están usando algunas funciones de ayuda como link-to o include-js que nos permiten generar elementos HTML con una sintaxis más tersa. Sería algo parecido a los HtmlHelpers de ASP.NET MVC.

Aquí se aprecia una de las ventajas de usar el mismo lenguaje para las plantillas que en el resto de la aplicación: podemos usar cualquier construcción del lenguaje dentro de las plantillas.

Un ejemplo de esto lo tenemos en la forma en que se pasan parámetros a la vista. Al tratarse una función más de clojure, los parámetros se pasan como argumentos de la función y luego se pueden procesar como en cualquier función. En este caso se usa un for para genear una lista de elementos con los nombres de nuestros héroes:

[:ul#heroes
  (for [name hero-names]
    [:li.hero-name (link-to "#" name)])]]

Para utilizar esta vista, debemos modificar una vez las rutas de la aplicación, quedando algo así:

(ns compojure-sample.handler
  (:use compojure.core)
  (:require [compojure.handler :as handler]
            [compojure.route :as route]
            ; Referenciamos el espacio de nombres de las vistas
            [compojure-sample.views :as view]))

(defroutes app-routes
  ; Enlazamos la función que acabamos de crear a la raíz del sitio web
  (GET "/" [] (view/index ["Chuck Norris", "Bruce Schneider", "Arturo Pérez Reverte"]))
  (route/files "/public" {:root "public"})
  (route/not-found "Not Found"))

Recordad que podemos comprobar que todo funciona ejecutando lein ring server. Además en caso de hacer cambios no es necesario reiniciar el servidor, sino que compojure los detectará y recompilará lo que sea necesario.

Próximos pasos

Ya tenemos una aplicación capaz de servir contenido estático y generar contenido dinámico. En el siguiente post veremos como crear el modelo interno de nuestra aplicación que nos permita gestionar la lista de héroes y facts, lo que nos permitirá profundizar un poco en técnicas para gestionar la mutabilidad en clojure.

El código fuente completo de este tutorial lo puedes encontrar en mi cuenta en github: https://github.com/jmhdez/compojure-sample. Siéntete libre de clonarlo, cambiarlo y jugar con él todo lo que quieras, y si te animas a realizar alguna corrección, estaré encantado de recibirla.

2 comentarios en “Tutorial Compojure II: Generando vistas con Hiccup

  1. Guillermo dijo:

    Qué ventajas tiene el uso de for en vez de usar map para generar los elementos de la lista?

  2. Hasta donde yo sé, en este caso es una cuestión de gustos. No sé si hay diferencias de rendimiento, pero para el tamaño de los datos supongo que serían insignificantes.

Comentarios cerrados.