# 7.4 Plantillas ## ¿Qué es una plantilla? Afortunadamente eres conciente del modelo MVC (Modelo, Vista, Controlador), donde los modelos procesan datos, las vistas muestran los restulados y los controladores manejan las peticiones. Para las vistas, muchos lenguajes dinámicos generan dataos escribiendo código en archivos HTML estáticos. Por ejemplo JSP, los implementa insertando `<%=...=%>`, PHP insertando ``, etc. La siguiente imagen muestra el mecanismo de las plantillas: ![](images/7.4.template.png?raw=true) Figura 7.1 Mecanismo de Plantillas La mayoría del contenido que las aplicaciones web responden a los clientes es estático, y el contenido dinámico usualmente es muy pequeño. Por ejemplo, si necesitas mostrar una lista de usuarios que han visitado la página, solo el nombre de usuario necesita ser dinámico. El estilo de la lista es el mismo. Como puedes ver, las plantillas son útiles para reusar contenido. ## Plantillas en Go En Go, tenemos el paquete `templata` que nos ayuda a manejar plantillas. Podemos usar funciones como `Pare`, `ParseFile` y `Execute` para cargar las plantillas desde texto plano o archivos, luego evaluar las partes dinámicas, como se muestra en la figura 7.1 Ejemplo: ``` func handler(w http.ResponseWriter, r *http.Request) { t := template.New("some template") // Create a template. t, _ = t.ParseFiles("tmpl/welcome.html", nil) // Parse template file. user := GetUser() // Get current user infomration. t.Execute(w, user) // merge. } ``` Como puedes ver, son muy fáciles de usar, cargar la información en plantillas de Go, como en cualquier otro lenguaje de programación. Para conveniencia, vamos a usar las siguientes reglas en nuestros ejemplos: - Usaremos `Parse` para reemplazar `ParseFiles`, porque `Parse` puede probar texto directamente desde cadenas, entonces no necesitaremos archivos extra. - Usaremos `main` en cada ejemplo, y no usaremos `handler`. - Usaremos `os.Stdout` para reemplazar `http.ResponseWriter`, desde que `os.Stdout` también implementa la interfaz `io.Writer`. ## Insertando información en las plantillas Hemos mostrado como puedes analizar y renderizar plantillas. Vamos a dar un paso mas adelante para renderizar información en nuestras plantillas. Cada plantilla es un objeto en Go, entonces ¿cómo insertamos campos en nuestras plantillas? ### Campos En Go, cada campo que intentas renderizar en una plantilla debería ser colocado dentro de `{{}}`. `{{.}}` es un atajo para el objeto actual, que es similar a su contraparte en Java o C++. Si necesitas accesar a los campos del objeto actual, deperías usar `{{.NombreDelCampo}}`. Nota que solament elos campos exportados pueden ser accesados en las plantillas. Aquí está un ejemplo: ``` package main import ( "html/template" "os" ) type Person struct { UserName string } func main() { t := template.New("fieldname example") t, _ = t.Parse("hello {{.UserName}}!") p := Person{UserName: "Astaxie"} t.Execute(os.Stdout, p) } ``` El ejemplo superior muestra `hello Astaxie` correctamente, pero si modificamos la estructura un poco, el siguiente error aparecerá: ``` type Person struct { UserName string email string // Field is not exported. } t, _ = t.Parse("hello {{.UserName}}! {{.email}}") ``` Esta parte del código no será compilado porque intenta acceder a un campo que no ha sido exportado. Sin embargo, si tratamos de usar un campo que no existe, Go simplemente mostrará una cadena vacía en vez de un error. Si imprimes `{{.}}` en una plantilla, Go mostrará una cadena formateada de este objeto, llamando `fmt` por debajo. ### Campos anidados Sabemos como mostrar un campo. ¿Qué pasa si el campo es un objeto y también tiene sus propios campos? ¿Cómo los imprimimos todos en un ciclo? Podemos usar `{{with ...}}... {{end}}` y `{{range ...}}{{end}}` para este propósito. - {% raw %}`{{range}}`{% endraw %} funciona como `range` en Go. - {% raw %}`{{with}}`{% endraw %} Permite escribir el mismo objeto una vez mas usando `.` como abreviación (***Similar al `with` de CB***). Mas ejemplos: ``` package main import ( "html/template" "os" ) type Friend struct { Fname string } type Person struct { UserName string Emails []string Friends []*Friend } func main() { f1 := Friend{Fname: "minux.ma"} f2 := Friend{Fname: "xushiwei"} t := template.New("fieldname example") t, _ = t.Parse(`hello {{.UserName}}! {{range .Emails}} an email {{.}} {{end}} {{with .Friends}} {{range .}} my friend name is {{.Fname}} {{end}} {{end}} `) p := Person{UserName: "Astaxie", Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, Friends: []*Friend{&f1, &f2}} t.Execute(os.Stdout, p) } ``` ### Condicionales Si necesitas verificar por condicionales en las plantillas, puedes usar la sintaxis `if-else` como lo haces en programas regulares en Go. Si el argumento estéa vacío, el valor por defecto del `if` es `false`. El siguiente ejemplo muestra como se usa `if-else` en las plantillas: ``` package main import ( "os" "text/template" ) func main() { tEmpty := template.New("template test") tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} will not be outputted. {{end}}\n")) tEmpty.Execute(os.Stdout, nil) tWithValue := template.New("template test") tWithValue = template.Must(tWithValue.Parse("Not empty pipeline if demo: {{if `anything`}} will be outputted. {{end}}\n")) tWithValue.Execute(os.Stdout, nil) tIfElse := template.New("template test") tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if part {{else}} else part.{{end}}\n")) tIfElse.Execute(os.Stdout, nil) } ``` Como puedes ver, es fácil usar `if-else` en las plantillas. ** Atención ** No puedes usar expresiones condicionales dentro del if, por ejemplo: `.Mail=="astaxie@gmail.com"`. Solo variables booleanas son aceptadas. ### Filtros (`|`) Los usuarios de Unix deben estar familiarizados con el operador `pipe`, como `ls | gre "beego"`. Este comando filtra archivos y solamente muestra los que contienen la palabra "beego". Una cosa que me gusta sobre las plantillas de Go es que soportan filtros. Cualquier cosa en `{{}}` puede ser información de los filtros. El e-mail que usamos para renderizar en nuestra aplicación puede ser vulnerable a un ataque XSS. ¿Cómo podemos prevenir esta problemática usando filtros? ``` {{. | html}} ``` Podemos usar este comando para escapar el cuerpo del correo a HTML. Es muy similar a trabajar con el comando de Unix, es muy conveniente en las funciones de plantillas. ### Variables de plantillas A veces necesitamos usar variables locales en las plantillas. Podemos usarlas con las palabras reservadas `with`, `range`, e `if`, y estará en el ámbito hasta que se use `{{end}}`. Aquí está un ejemplo de ocmo declarar una variable global: ``` $variable := pipeline ``` Mas ejemplos: ``` {{with $x := "output" | printf "%q"}}{{$x}}{{end}} {{with $x := "output"}}{{printf "%q" $x}}{{end}} {{with $x := "output"}}{{$x | printf "%q"}}{{end}} ``` ### Funciones de plantillas Go usa el paquete `fmt` para darle formato a la salida de las plantillas. pero a veces necesitamos hacer algo mas. Por ejemplo considera el siguiente escenario: vamos a decir que queremos reemplazar `@` por `de` en nuestra dirección de email, como `astaxie de beego.me`. En este punto tenemos que escribir nuestra función personalizada. Acada función de plantilla tiene un único nombre y está asociada con nuestro programa en Go como sigue: ``` type FuncMap map[string]interface{} ``` Supón que tienes una función de plantilla llamada `emailDeal` asociada con una función `EmailDealWith` en nuestro progrmaa en Go. Usamos el siguiente código para registrar esta función: ``` t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) ``` definición de `EmailDealWith`: ``` func EmailDealWith(args …interface{}) string ``` Ejemplo: ``` package main import ( "fmt" "html/template" "os" "strings" ) type Friend struct { Fname string } type Person struct { UserName string Emails []string Friends []*Friend } func EmailDealWith(args ...interface{}) string { ok := false var s string if len(args) == 1 { s, ok = args[0].(string) } if !ok { s = fmt.Sprint(args...) } // encontrar el símbolo @ substrs := strings.Split(s, "@") if len(substrs) != 2 { return s } // reemplazar el símbolo @ por " de " return (substrs[0] + " at " + substrs[1]) } func main() { f1 := Friend{Fname: "minux.ma"} f2 := Friend{Fname: "xushiwei"} t := template.New("fieldname example") t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) t, _ = t.Parse(`hello {{.UserName}}! {{range .Emails}} an emails {{.|emailDeal}} {{end}} {{with .Friends}} {{range .}} my friend name is {{.Fname}} {{end}} {{end}} `) p := Person{UserName: "Astaxie", Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, Friends: []*Friend{&f1, &f2}} t.Execute(os.Stdout, p) } ``` Aquí está una lista de las funciones de plantillas por defecto: ``` var builtins = FuncMap{ "and": and, "call": call, "html": HTMLEscaper, "index": index, "js": JSEscaper, "len": length, "not": not, "or": or, "print": fmt.Sprint, "printf": fmt.Sprintf, "println": fmt.Sprintln, "urlquery": URLQueryEscaper, } ``` ## Must El paquete de plantillas tiene una función llamada `Must` que es para validar plantillas, como verificar las llaves, comentarios y variables. Miremos un ejemplo de `Must`: ``` package main import ( "fmt" "text/template" ) func main() { tOk := template.New("first") template.Must(tOk.Parse(" algún texto estático /* y un comentario */")) fmt.Println("Analizis de la primera plantilla: OK.") template.Must(template.New("second").Parse("algún texto estático {{ .Name }}")) fmt.Println("Análisis de la segunda plantilla OK.") fmt.Println("La siguiente plantilla va a fallar.") tErr := template.New("Verificar el error de análisi con Must") template.Must(tErr.Parse(" some static text {{ .Name }")) } ``` Salida: ``` Analizis de la primera plantilla: OK. Análisis de la segunda plantilla OK. La siguiente plantilla va a fallar. panic: template: Verificar el error de análisi con Must:1: unexpected "}" in command ``` ## Plantillas anidadas Como en la mayoría de aplicaciones web, ciertas partes de las plantillas pueden ser reusada en otras plantillas, como encabezados, pies de páginas de un blog. Podemos declarar `header`, `content` y `footer` como sub plantillas y luego declararlas en go usando la siguiente sintaxis: ``` {{define "sub-template"}}content{{end}} ``` La subplantilla es llamada usando la siguiente sintaxis: ``` {{template "sub-template"}} ``` Aquí está un ejemplo completo, suponiendo que tenemos los siguientes tres archivos: `header.tmpl`, `content.tmpl` y `footer.tmpl` in la carpeta `templates`, también leeremos la carpeta y almacenaremos los nombres en un arreglo de cadenas, que luego usaremos para analizar los archivos. Plantilla principal: ``` {% raw %} //header.tmpl {{define "header"}} Something here {{end}} //content.tmpl {{define "content"}} {{template "header"}}

Nested here

{{template "footer"}} {{end}} //footer.tmpl {{define "footer"}} {{end}} // Cuando usamos subplantillas, asegúrate de haber analizado cada archivo de subplantillas, de otra manera el compilador no entenderá que sustituir cuando lea {{template "header"}} {% endraw %} ``` Código: ``` package main import ( "fmt" "os" "io/ioutil" "text/template" ) var templates *template.Template func main() { var allFiles []string files, err := ioutil.ReadDir("./templates") if err != nil { fmt.Println(err) } for _, file := range files { filename := file.Name() if strings.HasSuffix(filename, ".tmpl") { allFiles = append(allFiles, "./templates/"+filename) } } templates, err = template.ParseFiles(allFiles...) #parses all .tmpl files in the 'templates' folder s1 := templates.Lookup("header.tmpl") s1.ExecuteTemplate(os.Stdout, "header", nil) fmt.Println() s2 := templates.Lookup("content.tmpl") s2.ExecuteTemplate(os.Stdout, "content", nil) fmt.Println() s3 := templates.Lookup("footer.tmpl") s3.ExecuteTemplate(os.Stdout, "footer", nil) fmt.Println() s3.Execute(os.Stdout, nil) } ``` Como podemos ver aquí `template.ParseFiles` analiza todos las plantillas analizadas en un caché y cada plantilla definida por `{{define}}` es independiente de la otra. Ellas persisten en algo como un mapa, donde el nombre de la plantilla es la llave y el valor es el cuerpo de la plantilla. Podemos entonces usar `ExecuteTemplate` para ejecutar el subtemplate correspondiente, entonces el encavezado y el pié de página sin independientes y el contenido los tiene a ambos. Nota que si tratamos de ejecutar `s1.Execute` nada saldrá porque no hay ningún subtemplate disponible. Cuando quieras usar `define`, tienes que crear un archivo de texto con el mismo nombre de la subplantilla, por ejemplo `_head.tmpl` es una subplantila que se usa al rededor de tuproyecto, entonces crea este archivo en una carpeta para palanillas y úsala la sintaxis normal. El caché de verificación es creado básicamente para que no tengas que leer la plantilla cada vez que sirvas la petición, porque si lo haces, estás gastando un montón de recursos para leer un archivo que no cambia a menos que el código base sea reescrito, lo cual no tiene sentido para hacer en cada petición GET, entonces esta técnica es usada para analizar los archivos y luego usar un `Lookup()` en el caché para ejecutar la plantilla cuando se necesite mostrar la información. Las plantillas en un conjunto se pueden conocer unas a otras, pero debes analizar por cada conjunto en específico. Algunas veces vas a querer contextualizar las plantillas, por ejemplo, si tienes un `_head.html`, puedes tener un encabezado que que muestre información basada en loque estes cargando, por ejemplo una lista de tareas por hacer, que puede tener categorías como `pendiente`, `completada`, `eliminada` Supón que tienes el siguiente texto: ``` {{if eq .Navigation "pendiente"}} Tareas {{ else if eq .Navigation "completada"}}Completada {{ else if eq .Navigation "eliminada"}}Eliminada {{ else if eq .Navigation "editar"}} Editar {{end}} ``` Nota: Las platillas en Go siguen la notación polaca para realizar la comparación, donde tienes el operador primero y luego los valores de comparación. La parte del `else if` es igual. Típicamente, usamos un operador `{{range}}` para recorrer las variales de contexto las cuales son pasadas a la plantilla de la siguiente manera: ``` //present in views package context := db.GetTasks("pending") //true when you want non deleted notes homeTemplate.Execute(w, context) ``` Tenemos un objeto de contexto de la base de datos como una estructura, la definición está abajo: ``` // Estructura de tareas usada para la identificación de ellas type Task struct { Id int Title string Content string Created string } // Context es la estructura pasada a las plantillas type Context struct { Tasks []Task Navigation string Search string Message string } // Presente en el paquete de bases de Datos var task []types.Task var context types.Context context = types.Context{Tasks: task, Navigation: status} // Esta línea está en el paquete de bases de datos, donde el contexto es retornado a la vista ``` Usamos el arreglo de `Task` y `Navigation` en nuestras plantillas , vimos como usar `Navigation` en nuestras plantillas. Veremos como usar el arreglo e nuestra plantilla. Aquí en el ``{{if .Tast}}`` primero verificamos si el campo `Tasks` que pasamos a nuestra plantilla en el contexto está vacío o no. Si no está vacío, entonces colocamos `range` a través del arreglo para llenar el título y el contenido de cada `Task`. El ejemplo de abajo es muy importante en lo que se refiere a recorrer un arreglo en una plantilla, iniciamos usando un operador `Range`, luego le damos un miembro de la estructura como `{{.Name}}`, la estructura de `Task` tiene un `Title` y un `Content` (Nota que Title y Content tienen la primera letra en mayúscula, por lo tanto está exportadas). ``` {{ range .Tasks }} {{ .Title }} {{ .Content }} {{ end }} ``` Este bloque de código imprimirá el título y el contenido del arreglo de `Task` Debajo hay un ejemplo completo de `github.com/thewhitetulip/Tasks`. Plantilla home.html: ```
{{ if .Tasks}} {{range .Tasks}}

{{.Title}}


{{.Content}}

{{end}} {{else}}

No Tasks here

Create new task

{{end}} ``` ## Resumen En esta sección aprendiste como combinar datos dinámicos con plantillas usando técnicas de impresión de información en ciclos, funciones de plantillas y plantillas anidadas. Al aprender sobre las plantillas, podemos concluir la discusión de la V (Vista) de la arquitectura MVC. En los siguientes capítulos cubriremos los aspectos M (Modelo) y C (Controlador) del MVC. ## Enlaces - [Índice](preface.md) - Sección Anterior: [Expresiones regulares](07.3.md) - Siguiente sección: [Archivos](07.5.md)