logo Buffalo slack logo
Formularios
Frontend

Forms#

Buffalo usa el paquete github.com/gobuffalo/tags para facilitar la construcción de formulario.

Plush incluye dos helpers de este paquete que genera un formulario con estulo de Boostrap v3. Estos helpers son form y form_for.

Ambos tipos de helpers de formulario tienen las siguientes caracteristicas en común:

  • Configuración automática del token de autenticidad CSRF
  • Soporte para todos los métodos HTTP (PUT, POST, DELETE, etc…)
  • Manejo de errores
  • Soporte de formularios multipart
  • Tipos de entrada personalizables
  • Paso de atributos de etiquetas HTML

Formularios básicos#

El helper form se puede usar para generar formularios HTML. Dado que este tipo de formulario no está amarrado a ningún “modelo” en particular, toda la información se debe pasar como opciones al formulario y sus métodos.

templates/talks/edit.html
OUTPUT
// templates/talks/edit.html

<%= form({action: talkPath({id: 3}), method: "PUT"}) { %>
  <div class="row">
    <div class="col-md-12">
      <%= f.InputTag({name:"Title", value: talk.Title }) %>
    </div>

    <div class="col-md-6">
      <%= f.TextArea({value: talk.Abstract, hide_label: true }) %>
    </div>

    <div class="col-md-6">
      <%= f.SelectTag({name: "TalkFormatID", value: talk.TalkFormatID, options: talk_formats}) %>
      <%= f.SelectTag({name: "AudienceLevel", value: talk.AudienceLevel, options: audience_levels }) %>
    </div>

    <div class="col-md-12">
      <%= f.TextArea({name: "Description", value: talk.Description, rows: 10}) %>
    </div>
    <div class="col-md-12">
      <%= f.TextArea({notes:"Notes", value: talk.Notes, rows: 10 }) %>
    </div>

  </div>
<% } %>

Formularios de modelos#

El helper form_for se puede usar para generar formularios HTML para un modelo específico. Esto hace que el código sea más fácil de escribir, y mantiene un nivel de “consistencia” a través de tu aplicación.

El helper form_for se comporta similar al helper form, con algunas diferencias claves.

La primera diferencia es que el form_for toma un “modelo” como el primer argumento. Este “model” sólo necesita ser un struct, no tiene que estar respaldado por una base de datos.

La segunda diferencia está en la etiqueta que llama al modelo directamente. Estas etiquetas, como InputTag, toma el nombre del atributo del modelo para el que deseas contruir un campo, luego toma un grupo opcional de opciones como segundo argumento.

models/talk.go
templates/talks/edit.html
OUTPUT
// models/talk.go
type Talk struct {
  ID            int          `json:"id" db:"id"`
  CreatedAt     time.Time    `json:"created_at" db:"created_at"`
  UpdatedAt     time.Time    `json:"updated_at" db:"updated_at"`
  UserID        int          `json:"user_id" db:"user_id"`
  Title         string       `json:"title" db:"title"`
  Description   nulls.String `json:"description" db:"description"`
  Notes         nulls.String `json:"notes" db:"notes"`
  ParentID      nulls.Int    `json:"parent_id" db:"parent_id"`
  Abstract      string       `json:"abstract" db:"abstract"`
  AudienceLevel string       `json:"audience_level" db:"audience_level"`
  IsPublic      nulls.Bool   `json:"is_public" db:"is_public"`
  TalkFormatID  int          `json:"talk_format_id" db:"talk_format_id"`
}

Etiqueta Select#

Para construir tus etiquetas <select> dentro de los formularios Tags proporciona 3 formas convenientes de agregar tus opciones <select>: form.SelectOptions, map[string]interface{} o []string, todas ellas pasando un campo options a las opciones de form.SelectTag como:

<%= f.SelectTag("TalkFormatID", {options: talkFormats}) %>

o

<%= f.SelectTag("TalkFormatID", {options: ["one", "two"]}) %>

La cual usará el mismo valor del atributo value y el cuerpo de la opción, o:

<%= f.SelectTag("TalkFormatID", {options: {"one": 1, "two": 2}}) %>

La cual te permite definir el mapa de opciones dentro de la vista.

Interfaz Selectable#

Otra alternativa para las opciones de select es pasar una lista de estructuras que implementen la interfaz form.Selectable.

Que consiste en 2 métodos:

//Selectable allows any struct to become an option in the select tag.
type Selectable interface {
  SelectValue() interface{}
  SelectLabel() string
}

Al implementar esta interfaz, tags llamará a SelectValue y SelectLabel para obtener la opción Value y Lavel del implementador.

Selected#

Tags agregará el atributo selected a la opcion que tiene el mismo valor al que se recibe en la opción value del form.SelectTag, asi no tienes que morar la opción que tienen el mismo valor que la seleccionada manualmente, p.e:

<%= f.SelectTag("TalkFormatID", {options: {"one": 1, "two": 2}, value: 2}) %>

Produce:

<div class="form-group">
  <label>TalkFormatID</label>
  <select class="form-control" id="talk-TalkFormatID" name="TalkFormatID">
    <option value="1">one</option>
    <option value="2" selected>two</option>
  </select>
</div>

Y similarmente con el slice form.SelectOptions:

<%= f.SelectTag("TalkFormatID", {options: talkFormats, value: 2}) %>

Etiqueta Checkbox#

Tags proporciona una forma conveniente de construir un elemento HTML input con type="checkbox":

<%= f.CheckboxTag("IsPublic") %>

Que produce:

<div class="form-group">
  <label>
    <input class="" id="talk-IsPublic" name="IsPublic" type="checkbox" value="true" checked="">
    IsPublic
  </label>
</div>

Puedes cambiar facilmente el contenido del label con:

<%= f.CheckboxTag("IsPublic", {label: "Is the talk public?"}) %>

Que produce:

<div class="form-group">
  <label>
    <input class="" id="post-IsPublic" name="IsPublic" type="checkbox" value="true" checked="">
     Is the Talk public?
  </label>
</div>

Valores de Checkbox no marcado#

Por defecto, cuando un checkbox no está “marcado” no se enviará ningun valor al servidor. A menudo, es útil enviar un valor indicando que el checkbox no está marcado. Esto se puede establecer pasando un valor unchecked.

<%= f.CheckboxTag("IsPublic", {unchecked: false}) %>
<div class="form-group">
  <label>
    <input id="widget-IsPublic" name="IsPublic" type="checkbox" value="true">
    <input name="IsPublic" type="hidden" value="false"> IsPublic
  </label>
</div>

Cuando se envie el formulario, se enviará la etiqueta hidden y el servidor verá el valor falso.

Manejando Errores#

Tanto los ayudantes form como form_for tienen soporte para manejar los errores del paquete github.com/gobuffalo/validate.

En una acción simplemente establece un valor de tipo *validate.Errors en el contexto como errors y los helpers de formulario lo recogerán y añadirán mensajes de error a las etiquetas de formulario correspondientes.

actions/widgets.go
templates/widgets/new.html
OUTPUT
// actions/widgets.go
func (v WidgetsResource) Create(c buffalo.Context) error {
  tx := c.Value("tx").(*pop.Connection)
  widget := &models.Widget{}
  if err := c.Bind(widget); err != nil {
    return err
  }
  // Validate the data from the html form
  verrs, err := tx.ValidateAndCreate(widget)
  if err != nil {
    return errors.WithStack(err)
  }
  if verrs.HasAny() {
    c.Set("widget", widget)
    // Make the errors available inside the html template
    c.Set("errors", verrs)
    return c.Render(422, r.HTML("widgets/new.html"))
  }
  c.Flash().Add("success", "Widget was created successfully")
  return c.Redirect(302, "/widgets/%s", widget.ID)
}

Uso de helpers de formulario que no son de Bootstrap#

Los helpers por defecto, form y form_for generan formularios compatibles con Boostrap 3. Si esto no es para ti, puedes usar fácilmente la version no-Boostrap de estos helpers.

Requiere la versión de Plush v3.4.8 o superior
actions/render.go
templates/widgets/new.html
OUTPUT
// actions/render.go
func init() {
  r = render.New(render.Options{
    // ...
    // Add template helpers here:
    Helpers: render.Helpers{
      "form":     plush.FormHelper,
      "form_for": plush.FormForHelper,
    },
    // ...
  })
}

Preguntas frecuentes#

¿Cómo asignar un formulario a un modelo/estructura?#

Consulta la página Vinculación de Peticiones para mas información sobre la vinculación de peticiones.

¿Puedo cambiar el nombre de la variable f en mi plantilla?#

Por defecto, el valor del formulario dentro del bloque tiene por nombre f, sin embargo, este lo puedes cambiar cuando creas el formulario y pasas la opción var.

<%= form({var: "xyz"}) { %>
  <%= xyz.InputTag({name: "Foo"}) %>
<% } %>

Como puedo crear un formulario Multipart?#

<%= form({multipart: true}) { %>
<% } %>
<form enctype="multipart/form-data" method="POST">
</form>

Puedo solo usar mi propio formulario (Sin usar el helper de formulario)?#

Si, puedes crear tu propio formulario. Los formularios son proporcionados para qeu Buffalo genere tus recursos son simplemente un marcador de pocision para que puedas ponerte en marcha rápidamente. Sin embargo, es importante tener en cuenta que pedir a Buffalo que genere tus recursos, usando generadores suministrados, tambien generará las rutas relacionadas con los CRUD’s. Esto es importante ya que la ruta asociada a la acción UPDATE hace uso del método PUT y no es un valor válido para un método de formulario HTML según el HTML Standard. Dicho esto, debes asegurarte de estructurar tu formulario (para editar un recurso) para usar el método POST para conectar con el método HTTP, mientras usas una entrada oculta para indicar tu intención de hacer uso del método PUT del lado del servidor. Un ejemplo de esto sería el siguiente:

<form method="POST" ...>
  <input type="hidden" name="_method" value="PUT" />
...

Cómo puedo manejar los tokens CSRF si utilizo mi propio formulario?#

Si decides usar tus propios formularios, necesitarás una forma de proporcionarle al formulario el token de autenticidad. Hay dos formas de resolver este problema.

La primera forma es usar el authenticity_token directamente en el formulario, puesto que ya está en el contexto.

<form method="POST" ...>
  <input name="authenticity_token" type="hidden" value="<%= authenticity_token %>">
</form>

Otra forma es escribir el helper que genere esa linea de codigo por ti.

"csrf": func(ctx plush.HelperContext) (template.HTML, error) {
  tok, ok := ctx.Value("authenticity_token").(string)
  if !ok {
    return "", fmt.Errorf("expected CSRF token got %T", ctx.Value("authenticity_token"))
  }
  t := tags.New("input", tags.Options{
    "value": tok,
    "type":  "hidden",
    "name":  "authenticity_token",
  })
  return t.HTML(), nil
},

Ahora que has definido un helper para usarlo en tus plantillas puedes usar tu helper dentro de tu formulario con <%= csrf() %>. Así que tu formulario personalizado debería terminar viéndose así:

<form method="POST" ...>
  <%= csrf() %>
</form>