10. Go web: enlaces a página anterior y siguiente

Publicado por

En las entradas anteriores necesitábamos escribir la URL con la ruta de acceso completa porque no nos era posible navegar entre las diferentes páginas que ya teníamos almacenadas a menos que supiéramos de antemano que existían. En esta entrada vamos a agregar un par de enlaces al final de las wiki cuya función será direccionar a las demás. Cabe señalar que para ejemplificar este funcionamiento, opté por enlazar a las páginas anterior y siguiente en orden alfabético pero tú podrías hacerlo, por ejemplo, con enlaces a páginas de temas relacionados o por fecha.

Enlazando wikis

Comenzamos por agregar 4 miembros nuevos a nuestro registro (struct) Pagina:

type Pagina struct{
  Titulo string
  Cuerpo []byte

  Siguiente string
  Visibilidad_A string
  Anterior string
  Visibilidad_S string
}

Las cadenas (string) Anterior y Siguiente son las que contendrán los nombres de las respectivas notas anterior y siguiente. Agregué dos miembros más, Visibilidad_A y Visibilidad_S, para permitir esconder los enlaces cuando no haya ninguna página a la cual direccionar, esto por medio del atributo style. Para que estos cambios sean visibles, modificamos la plantilla view.html así:

<h1>{{.Titulo}}</h1>
[<a href="/edit/{{.Titulo}}">edit</a>]
<div>{{printf "%s" .Cuerpo}}</div>
<div>[<a style="visibility: {{.Visibilidad_A}};" href="/view/{{.Anterior}}">Anterior</a>]
[<a style="visibility: {{.Visibilidad_S}};" href="/view/{{.Siguiente}}">Siguiente</a>]</div>

Como puedes darte cuenta, en la plantilla solamente agregamos un par de enlaces al final con estilo modificado. A este estilo se le conoce como propiedades CSS, en nuestro caso modificamos la visibilidad (visibility) de cada enlace por separado.

En seguida, agregamos un slice global que será el encargado de almacenar los nombres de las páginas del servidor. Hay muchas otras alternativas para hacer esta tarea, en mi caso opté por almacenar los nombres de las páginas en memoria para no tener que leer muchas veces el disco.

var enlaces []string

A continuación, creamos la función encargada de leer los nombres de las wikis al inicio de la ejecución del servidor, yo la llamaré iniciarEnlaces():

func iniciarEnlaces(){
  paginas, _ := ioutil.ReadDir("./view/")
  enlaces = make([]string, 0, 0)

  for _, actual := range paginas {
    if(actual.Name() != (pagina_principal + ".txt")){
      enlaces = append(enlaces, actual.Name()[ : len(actual.Name()) - 4 ])
    }
  }
}

Con ayuda de ioutil.ReadDir() consultamos los nombres de las páginas que actualmente existen en la carpeta donde las guardamos y, además, aprovechamos para inicializar el slice que los contendrá en memoria. Iteramos entre las lecturas de ioutil.ReadDir(), extraemos el nombre de los archivos que fueron leídos y los guardamos en el slice (los nombres contendrán la extensión .txt, por ello eliminamos los últimos 4 caracteres antes de agregarlos).

La función anterior nos permite leer los nombres de las páginas que teníamos antes de la ejecución del servidor pero no nos permite agregar nuevos nombres en tiempo de ejecución, para ello creamos la siguiente función:

func agregarEnlace(pagina string){
  for _, actual := range enlaces{
    if (actual == pagina || actual == pagina_principal) {
      return
    }
  }
  enlaces = append(enlaces, pagina)
  sort.Strings(enlaces)
}

Cuando intentamos guardar el nombre de una página, es necesario verificar que no existía previamente o que no sea la página principal ya que esta es tratada de forma distinta, en cuyo caso no se agrega el nombre y la función regresa sin hacer cambios. En lugar de un slice puedes usar un map si así lo deseas, la ventaja de estos es que te permiten garantizar que los elementos no se repiten sin programar nada. Utilizamos el método sort.Strings() ubicado dentro del paquete sort para ordenar las cadenas del slice alfabéticamente, para ello lo importamos junto con los demás paquetes que ya teníamos de entradas anteriores:

import (
  "fmt"
  "io/ioutil"
  "net/http"
  "html/template"
  "regexp"
  "errors"
  "sort" //Nuevo
)

Para continuar, necesitamos obtener los nombres de las páginas a las que vamos a direccionar al cliente desde la página actual. Para lograrlo construimos la siguiente función:

func obtenerEnlaces(pagina string) (string, string) {
  var ant, sig string

  for i, actual := range enlaces {
    if(actual == pagina){
      if(i > 0){
        ant = enlaces[i - 1]
      } else{
        ant = ""
      }

      if(i < (len(enlaces)-1) ){
        sig = enlaces[i + 1]
      } else {
        sig = ""
      }
      break
    }
  }
  return ant, sig
}

Resumiendo, esta función obtiene la posición dentro del slice del nombre que se le pasa como argumento y devuelve los nombres de las páginas anterior y siguiente. Si la página con la que se trabaja es la primera o la última, utilizamos la cadena vacía para especificar que no hay nada antes o después según corresponda (tú puedes optar por otras alternativas como direccionar a la primera página cuando se alcance la última).

Construimos un método para los registros Pagina que sea el encargado de asignar los valores de visibilidad a los enlaces:

func ( p* Pagina ) asignarVisibilidad() {
  if(p.Anterior == ""){
    p.Visibilidad_A = "hidden"
  }else {
    p.Visibilidad_A = "visible"
  }
  if(p.Siguiente == ""){
    p.Visibilidad_S = "hidden"
  }else {
    p.Visibilidad_S = "visible"
  }
}

La propiedad CSS visibility del atributo style nos dice que cuando esta es hidden, entonces el contenido no se mostrará en pantalla; en cambio, si esta es visible (por defecto), entonces el contenido sí se mostrará en pantalla.

Finalizando los cambios en nuestro código fuente, agregamos los llamados a las funciones que creamos anteriormente según corresponda:

//Cuando guardamos una página, intentamos agregar un nombre nuevo
func ( p* Pagina ) guardar() error {
  nombre := p.Titulo + ".txt"

  agregarEnlace(p.Titulo) //Nuevo

  return ioutil.WriteFile( "./view/" + nombre, p.Cuerpo, 0600)
}

//Cuando cargamos una ṕágina, obtenemos los enlaces
func cargarPagina( titulo string ) (*Pagina, error) {
  nombre_archivo := titulo + ".txt"
  fmt.Println("El cliente ha pedido: " + nombre_archivo)
  cuerpo, err := ioutil.ReadFile( "./view/" + nombre_archivo )

  if err != nil {
    return nil, err
  }

  ant, sig := obtenerEnlaces(titulo) //Nuevo

  //Modificado
  return &Pagina{Titulo: titulo, Cuerpo: cuerpo, Anterior: ant, Siguiente: sig}, nil
}

//Llamamos asignar visibilidad desde el manejador para mostrar
func manejadorMostrar(w http.ResponseWriter, r *http.Request, titulo string){
  p, err := cargarPagina(titulo)

  if err != nil {
    http.Redirect(w, r, "/edit/" + titulo, http.StatusFound)
    fmt.Println("La página solicitada no existía. Llamando al editor...")
    return
  }

  p.asignarVisibilidad() //Nuevo
  cargarPlantilla(w, "view", p)
}

Finalizando…

Esto ha sido todo por esta vez, espero que te haya servido, nos vemos la próxima, see ya!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *