Skip to content

Latest commit

 

History

History
463 lines (391 loc) · 17.6 KB

File metadata and controls

463 lines (391 loc) · 17.6 KB

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 <?php ... ?>, 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"}}
	<html>
	<head>
		<title>Something here</title>
	</head>
	<body>
	{{end}}

	//content.tmpl
	{{define "content"}}
	{{template "header"}}
	<h1>Nested here</h1>
	<ul>
		<li>Nested usag</li>
		<li>Call template</li>
	</ul>
	{{template "footer"}}
	{{end}}

	//footer.tmpl
	{{define "footer"}}
	</body>
	</html>
	{{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:

    <title>{{if eq .Navigation "pendiente"}} Tareas
           {{ else if eq .Navigation "completada"}}Completada
           {{ else if eq .Navigation "eliminada"}}Eliminada
           {{ else if eq .Navigation "editar"}} Editar
           {{end}}
     </title>

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:

    <div class="timeline">
    {{ if .Tasks}} {{range .Tasks}}
    <div class="note">
      <p class="noteHeading">{{.Title}}</p>
      <hr>
      <p class="noteContent">{{.Content}}</p>
      </ul>
      </span>
    </div>
    {{end}} {{else}}
    <div class="note">
      <p class="noteHeading">No Tasks here</p>
      <p class="notefooter">
      Create new task<button class="floating-action-icon-add" > here </button> </p>
    </div>
    {{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