# 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:  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"}}
{{.Title}}
{{.Content}}
No Tasks here