Module: Hanami::Helpers::FormHelper

Includes:
HtmlHelper
Defined in:
lib/hanami/helpers/form_helper.rb,
lib/hanami/helpers/form_helper/values.rb,
lib/hanami/helpers/form_helper/html_node.rb,
lib/hanami/helpers/form_helper/form_builder.rb

Overview

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>

Examples:

One output block (template)

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

    submit 'Create'
  end
%>

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 %>

See Also:

Since:

  • 0.2.0

Defined Under Namespace

Classes: Form, FormBuilder, HtmlNode, Values

Constant Summary collapse

DEFAULT_METHOD =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Default HTTP method for form

Since:

  • 0.2.0

'POST'.freeze
DEFAULT_CHARSET =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Default charset

Since:

  • 0.2.0

'utf-8'.freeze
CSRF_TOKEN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

CSRF Token session key

This key is shared with hanamirb, hanami-controller.

Since:

  • 0.2.0

:_csrf_token

Instance Method Summary collapse

Instance Method Details

#csrf_meta_tagsHanami::Helpers::HtmlHelper::HtmlBuilder, NilClass

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

Examples:

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

<html>
  <head>
    <!-- ... -->
    <meta name="csrf-param" content="_csrf_token">
    <meta name="csrf-token" content="4a038be85b7603c406dcbfad4b9cdf91ec6ca138ed6441163a07bb0fdfbe25b5">
  </head>
  <!-- ... -->
</html>

Returns:

Since:

  • 1.2.0



485
486
487
488
489
490
# 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_tokenString, NilClass

Returns CSRF Protection Token stored in session.

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

Returns:

  • (String, NilClass)

    token, if present

Since:

  • 0.2.0



454
455
456
457
458
459
460
# 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, options, &blk) ⇒ Hanami::Helpers::FormHelper::FormBuilder #form_for(form, attributes = {}, &blk) ⇒ Hanami::Helpers::FormHelper::FormBuilder

Instantiate a HTML5 form builder

Examples:

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>

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>

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>

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>

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>

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>

Overloads:

  • #form_for(name, url, options, &blk) ⇒ Hanami::Helpers::FormHelper::FormBuilder

    Use inline values

    Parameters:

    • name (Symbol)

      the toplevel name of the form, it’s used to generate input names, ids, and to lookup params to fill values.

    • url (String)

      the form action URL

    • options (Hash)

      HTML attributes to pass to the form tag and form values

    • blk (Proc)

      A block that describes the contents of the form

    Options Hash (options):

    • :values (Hash)

      An optional payload of objects to pass

    • :params (Hash)

      An optional override of params

  • #form_for(form, attributes = {}, &blk) ⇒ Hanami::Helpers::FormHelper::FormBuilder

    Use Form

    Parameters:

    • form (Hanami::Helpers::FormHelper::Form)

      a form object

    • attributes (Hash) (defaults to: {})

      HTML attributes to pass to the form tag and form values

    • blk (Proc)

      A block that describes the contents of the form

Returns:

See Also:

Since:

  • 0.2.0



431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# 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