module Hanami::Helpers::FormHelper

Form builder

By including Hanami::Helpers::FormHelper it will inject one public method: form_for. This is a HTML5 form builder.

To understand the general HTML5 builder syntax of this framework, please consider to have a look at Hanami::Helpers::HtmlHelper documentation.

This builder is independent from any template engine. This was hard to achieve without a compromise: the form helper should be used in one output block in a template or as a method in a view (see the examples below).

Features:

* Support for complex markup without the need of concatenation
* Auto closing HTML5 tags
* Support for view local variables
* Method override support (PUT/PATCH/DELETE HTTP verbs aren't understood by browsers)
* Automatic generation of HTML attributes for inputs: <tt>id</tt>, <tt>name</tt>, <tt>value</tt>
* Allow to override HTML attributes
* Extract values from request params and fill <tt>value</tt> attributes
* Automatic selection of current value for radio button and select inputs
* Infinite nested fields

Supported tags and inputs:

* <tt>check_box</tt>
* <tt>color_field</tt>
* <tt>date_field</tt>
* <tt>datetime_field</tt>
* <tt>datetime_local_field</tt>
* <tt>email_field</tt>
* <tt>fields_for</tt>
* <tt>file_field</tt>
* <tt>form_for</tt>
* <tt>hidden_field</tt>
* <tt>label</tt>
* <tt>number_field</tt>
* <tt>password_field</tt>
* <tt>radio_button</tt>
* <tt>select</tt>
* <tt>submit</tt>
* <tt>text_area</tt>
* <tt>text_field</tt>

@since 0.2.0

@see Hanami::Helpers::FormHelper#form_for @see Hanami::Helpers::HtmlHelper

@example One output block (template)

<%=
  form_for :book, routes.books_path do
    text_field :title

    submit 'Create'
  end
%>

@example Method (view)

require 'hanami/helpers'

class MyView
  include Hanami::Helpers::FormHelper

  def my_form
    form_for :book, routes.books_path do
      text_field :title
    end
  end
end

<!-- use this in the template -->
<%= my_form %>

Constants

CSRF_TOKEN

CSRF Token session key

This key is shared with hanamirb, hanami-controller.

@since 0.2.0 @api private

DEFAULT_CHARSET

Default charset

@since 0.2.0 @api private

DEFAULT_METHOD

Default HTTP method for form

@since 0.2.0 @api private

Public Instance Methods

csrf_meta_tags() click to toggle source

Prints CSRF meta tags for Unobtrusive JavaScript (UJS) purposes.

@return [Hanami::Helpers::HtmlHelper::HtmlBuilder,NilClass] the tags if `csrf_token` is not `nil`

@since 1.2.0

@example

<html>
  <head>
    <!-- ... -->
    <%= csrf_meta_tags %>
  </head>
  <!-- ... -->
</html>

<html>
  <head>
    <!-- ... -->
    <meta name="csrf-param" content="_csrf_token">
    <meta name="csrf-token" content="4a038be85b7603c406dcbfad4b9cdf91ec6ca138ed6441163a07bb0fdfbe25b5">
  </head>
  <!-- ... -->
</html>
# File lib/hanami/helpers/form_helper.rb, line 485
def csrf_meta_tags
  return if csrf_token.nil?

  html.meta(name: "csrf-param", content: CSRF_TOKEN) +
    html.meta(name: "csrf-token", content: csrf_token)
end
csrf_token() click to toggle source

Returns CSRF Protection Token stored in session.

It returns nil if sessions aren't enabled or the value is missing.

@return [String,NilClass] token, if present

@since 0.2.0

# File lib/hanami/helpers/form_helper.rb, line 454
def csrf_token
  if defined?(session)
    session[CSRF_TOKEN]
  elsif defined?(locals) && locals[:session]
    locals[:session][CSRF_TOKEN]
  end
end
form_for(name, url = nil, options = {}, &blk) click to toggle source

Instantiate a HTML5 form builder

@overload form_for(name, url, options, &blk)

Use inline values
@param name [Symbol] the toplevel name of the form, it's used to generate
  input names, ids, and to lookup params to fill values.
@param url [String] the form action URL
@param options [Hash] HTML attributes to pass to the form tag and form values
@option options [Hash] :values An optional payload of objects to pass
@option options [Hash] :params An optional override of params
@param blk [Proc] A block that describes the contents of the form

@overload form_for(form, attributes = {}, &blk)

Use Form
@param form [Hanami::Helpers::FormHelper::Form] a form object
@param attributes [Hash] HTML attributes to pass to the form tag and form values
@param blk [Proc] A block that describes the contents of the form

@return [Hanami::Helpers::FormHelper::FormBuilder] the form builder

@since 0.2.0

@see Hanami::Helpers::FormHelper @see Hanami::Helpers::FormHelper::Form @see Hanami::Helpers::FormHelper::FormBuilder

@example Inline Values In Template

<%=
  form_for :book, routes.books_path, class: 'form-horizontal' do
    div do
      label      :title
      text_field :title, class: 'form-control'
    end

    submit 'Create'
  end
%>

<!-- output -->

<form action="/books" method="POST" accept-charset="utf-8" id="book-form" class="form-horizontal">
  <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
  <div>
    <label for="book-title">Title</label>
    <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
  </div>

  <button type="submit">Create</button>
</form>

@example Use In A View

module Web::Views::Books
  class New

  def form
    form_for :book, routes.books_path, class: 'form-horizontal' do
      div do
        label      :title
        text_field :title, class: 'form-control'
      end

      submit 'Create'
    end
  end
end

<!-- in the corresponding template use this -->
<%= form %>

<!-- output -->

<form action="/books" method="POST" accept-charset="utf-8" id="book-form" class="form-horizontal">
  <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
  <div>
    <label for="book-title">Title</label>
    <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
  </div>

  <button type="submit">Create</button>
</form>

@example Share Code Between Views

# Given the following views to create and update a resource
module Web::Views::Books
  class New
    include Web::View

    def form
      Form.new(:book, routes.books_path)
    end

    def submit_label
      'Create'
    end
  end

  class Edit
    include Web::View

    def form
      Form.new(:book, routes.book_path(id: book.id),
        {book: book}, {method: :patch})
    end

    def submit_label
      'Update'
    end
  end
end

# The respective templates can be identical:

## books/new.html.erb
<%= render partial: 'books/form' %>

## books/edit.html.erb
<%= render partial: 'books/form' %>

# While the partial can have the following markup:

## books/_form.html.erb
<%=
  form_for form, class: 'form-horizontal' do
    div do
      label      :title
      text_field :title, class: 'form-control'
    end

    submit submit_label
  end
%>

<!-- output -->

<form action="/books" method="POST" accept-charset="utf-8" id="book-form" class="form-horizontal">
  <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
  <div>
    <label for="book-title">Title</label>
    <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
  </div>

  <button type="submit">Create</button>
</form>

@example Method override

<%=
  form_for :book, routes.book_path(id: book.id), method: :put do
    text_field :title

    submit 'Update'
  end
%>

<!-- output -->

<form action="/books/23" accept-charset="utf-8" id="book-form" method="POST">
  <input type="hidden" name="_method" value="PUT">
  <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
  <input type="text" name="book[title]" id="book-title" value="Test Driven Development">

  <button type="submit">Update</button>
</form>

@example Nested fields

<%=
  form_for :delivery, routes.deliveries_path do
    text_field :customer_name

    fields_for :address do
      text_field :city
    end

    submit 'Create'
  end
%>

<!-- output -->

<form action="/deliveries" accept-charset="utf-8" id="delivery-form" method="POST">
  <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
  <input type="text" name="delivery[customer_name]" id="delivery-customer-name" value="">
  <input type="text" name="delivery[address][city]" id="delivery-address-city" value="">

  <button type="submit">Create</button>
</form>

@example Override params

<%=
  form_for :song, routes.songs_path, params: { song: { title: "Envision" } } do
    text_field :title

    submit 'Create'
  end
%>

<!-- output -->

<form action="/songs" accept-charset="utf-8" id="song-form" method="POST">
  <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
  <input type="text" name="song[title]" id="song-title" value="Envision">

  <button type="submit">Create</button>
</form>
# File lib/hanami/helpers/form_helper.rb, line 431
def form_for(name, url = nil, options = {}, &blk) # rubocop:disable Metrics/MethodLength
  form = if name.is_a?(Form)
           options = url || {}
           name
         else
           Form.new(name, url, options.delete(:values))
         end

  params = options.delete(:params)
  opts = options.dup
  opts[:"data-remote"] = opts.delete(:remote) if opts.key?(:remote)
  attributes = { action: form.url, method: form.verb, 'accept-charset': DEFAULT_CHARSET, id: "#{form.name}-form" }.merge(opts)

  FormBuilder.new(form, attributes, self, params, &blk)
end