hanami-helpers 0.0.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/LICENSE.md +22 -0
- data/README.md +362 -9
- data/hanami-helpers.gemspec +16 -12
- data/lib/hanami-helpers.rb +1 -0
- data/lib/hanami/helpers.rb +28 -2
- data/lib/hanami/helpers/escape_helper.rb +271 -0
- data/lib/hanami/helpers/form_helper.rb +424 -0
- data/lib/hanami/helpers/form_helper/form_builder.rb +911 -0
- data/lib/hanami/helpers/form_helper/html_node.rb +79 -0
- data/lib/hanami/helpers/form_helper/values.rb +38 -0
- data/lib/hanami/helpers/html_helper.rb +207 -0
- data/lib/hanami/helpers/html_helper/empty_html_node.rb +92 -0
- data/lib/hanami/helpers/html_helper/html_builder.rb +376 -0
- data/lib/hanami/helpers/html_helper/html_fragment.rb +45 -0
- data/lib/hanami/helpers/html_helper/html_node.rb +70 -0
- data/lib/hanami/helpers/html_helper/text_node.rb +33 -0
- data/lib/hanami/helpers/link_to_helper.rb +135 -0
- data/lib/hanami/helpers/number_formatting_helper.rb +220 -0
- data/lib/hanami/helpers/routing_helper.rb +52 -0
- data/lib/hanami/helpers/version.rb +4 -1
- metadata +60 -14
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -2
- data/bin/console +0 -14
- data/bin/setup +0 -8
data/hanami-helpers.gemspec
CHANGED
@@ -4,20 +4,24 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'hanami/helpers/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'hanami-helpers'
|
8
8
|
spec.version = Hanami::Helpers::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
9
|
+
spec.authors = ['Luca Guidi', 'Trung Lê', 'Alfonso Uceda']
|
10
|
+
spec.email = ['me@lucaguidi.com', 'trung.le@ruby-journal.com', 'uceda73@gmail.com']
|
11
|
+
spec.summary = %q{Hanami helpers}
|
12
|
+
spec.description = %q{View helpers for Ruby applications}
|
13
|
+
spec.homepage = 'http://hanamirb.org'
|
14
|
+
spec.license = 'MIT'
|
11
15
|
|
12
|
-
spec.
|
13
|
-
spec.
|
14
|
-
spec.
|
16
|
+
spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md hanami-helpers.gemspec`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
spec.required_ruby_version = '>= 2.0.0'
|
15
21
|
|
16
|
-
spec.
|
17
|
-
spec.bindir = "exe"
|
18
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
-
spec.require_paths = ["lib"]
|
22
|
+
spec.add_dependency 'hanami-utils', '~> 0.7'
|
20
23
|
|
21
|
-
spec.add_development_dependency
|
22
|
-
spec.add_development_dependency
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
spec.add_development_dependency 'minitest', '~> 5.5'
|
23
27
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'hanami/helpers'
|
data/lib/hanami/helpers.rb
CHANGED
@@ -1,7 +1,33 @@
|
|
1
|
-
require
|
1
|
+
require 'hanami/helpers/version'
|
2
|
+
require 'hanami/helpers/html_helper'
|
3
|
+
require 'hanami/helpers/escape_helper'
|
4
|
+
require 'hanami/helpers/routing_helper'
|
5
|
+
require 'hanami/helpers/link_to_helper'
|
6
|
+
require 'hanami/helpers/form_helper'
|
7
|
+
require 'hanami/helpers/number_formatting_helper'
|
2
8
|
|
3
9
|
module Hanami
|
10
|
+
# View helpers for Ruby applications
|
11
|
+
#
|
12
|
+
# @since 0.1.0
|
4
13
|
module Helpers
|
5
|
-
#
|
14
|
+
# Override for Module.included
|
15
|
+
#
|
16
|
+
# It injects all the available helpers.
|
17
|
+
#
|
18
|
+
# @since 0.1.0
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
# @see http://www.ruby-doc.org/core/Module.html#method-i-included
|
22
|
+
def self.included(base)
|
23
|
+
base.class_eval do
|
24
|
+
include Hanami::Helpers::HtmlHelper
|
25
|
+
include Hanami::Helpers::EscapeHelper
|
26
|
+
include Hanami::Helpers::RoutingHelper
|
27
|
+
include Hanami::Helpers::LinkToHelper
|
28
|
+
include Hanami::Helpers::FormHelper
|
29
|
+
include Hanami::Helpers::NumberFormattingHelper
|
30
|
+
end
|
31
|
+
end
|
6
32
|
end
|
7
33
|
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
require 'hanami/utils/escape'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Helpers
|
5
|
+
# Escape helpers
|
6
|
+
#
|
7
|
+
# You can include this module inside your view and
|
8
|
+
# the view will have access all methods.
|
9
|
+
#
|
10
|
+
# By including <tt>Hanami::Helpers::EscapeHelper</tt> it will inject private
|
11
|
+
# methods as markup escape utilities.
|
12
|
+
#
|
13
|
+
# @since 0.1.0
|
14
|
+
module EscapeHelper
|
15
|
+
private
|
16
|
+
# Escape the given HTML tag content.
|
17
|
+
#
|
18
|
+
# This should be used only for untrusted contents: user input.
|
19
|
+
#
|
20
|
+
# This should be used only for tag contents.
|
21
|
+
# To escape tag attributes please use <tt>Hanami::Helpers::EscapeHelper#escape_html_attribute</tt>.
|
22
|
+
#
|
23
|
+
# @param input [String] the input
|
24
|
+
#
|
25
|
+
# @return [String] the escaped string
|
26
|
+
#
|
27
|
+
# @since 0.1.0
|
28
|
+
#
|
29
|
+
# @see Hanami::Helpers::EscapeHelper#escape_html_attribute
|
30
|
+
#
|
31
|
+
# @example Basic usage
|
32
|
+
# require 'hanami/helpers/escape_helper'
|
33
|
+
#
|
34
|
+
# class MyView
|
35
|
+
# include Hanami::Helpers::EscapeHelper
|
36
|
+
#
|
37
|
+
# def good_content
|
38
|
+
# h "hello"
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def evil_content
|
42
|
+
# h "<script>alert('xss')</script>"
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# view = MyView.new
|
47
|
+
#
|
48
|
+
# view.good_content
|
49
|
+
# # => "hello"
|
50
|
+
#
|
51
|
+
# view.evil_content
|
52
|
+
# # => "<script>alert('xss')</script>"
|
53
|
+
#
|
54
|
+
# @example With HTML builder
|
55
|
+
# #
|
56
|
+
# # CONTENTS ARE AUTOMATICALLY ESCAPED
|
57
|
+
# #
|
58
|
+
# require 'hanami/helpers'
|
59
|
+
#
|
60
|
+
# class MyView
|
61
|
+
# include Hanami::Helpers
|
62
|
+
#
|
63
|
+
# def evil_content
|
64
|
+
# html.div do
|
65
|
+
# "<script>alert('xss')</script>"
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# view = MyView.new
|
71
|
+
# view.evil_content
|
72
|
+
# # => "<div>\n<script>alert('xss')</script></div>"
|
73
|
+
def escape_html(input)
|
74
|
+
Utils::Escape.html(input)
|
75
|
+
end
|
76
|
+
|
77
|
+
# @since 0.1.0
|
78
|
+
alias_method :h, :escape_html
|
79
|
+
|
80
|
+
# Escape the given HTML tag attribute.
|
81
|
+
#
|
82
|
+
# This MUST be used for escaping HTML tag attributes.
|
83
|
+
#
|
84
|
+
# This should be used only for untrusted contents: user input.
|
85
|
+
#
|
86
|
+
# This can also be used to escape tag contents, but it's slower.
|
87
|
+
# For this purpose use <tt>Hanami::Helpers::EscapeHelper#escape_html</tt>.
|
88
|
+
#
|
89
|
+
# @param input [String] the input
|
90
|
+
#
|
91
|
+
# @return [String] the escaped string
|
92
|
+
#
|
93
|
+
# @since 0.1.0
|
94
|
+
#
|
95
|
+
# @see Hanami::Helpers::EscapeHelper#escape_html
|
96
|
+
#
|
97
|
+
# @example Basic usage
|
98
|
+
# require 'hanami/helpers/escape_helper'
|
99
|
+
#
|
100
|
+
# class MyView
|
101
|
+
# include Hanami::Helpers::EscapeHelper
|
102
|
+
#
|
103
|
+
# def good_attribute
|
104
|
+
# attribute = "small"
|
105
|
+
#
|
106
|
+
# %(<span class="#{ ha(attribute) }">hello</span>
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# def evil_attribute
|
110
|
+
# attribute = %(" onclick="javascript:alert('xss')" id=")
|
111
|
+
#
|
112
|
+
# %(<span class="#{ ha(attribute) }">hello</span>
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# view = MyView.new
|
117
|
+
#
|
118
|
+
# view.good_attribute
|
119
|
+
# # => %(<span class="small">hello</span>)
|
120
|
+
#
|
121
|
+
# view.evil_attribute
|
122
|
+
# # => %(<span class="" onclick="javascript:alert('xss')" id="">hello</span>
|
123
|
+
#
|
124
|
+
# @example With HTML builder
|
125
|
+
# #
|
126
|
+
# # ATTRIBUTES AREN'T AUTOMATICALLY ESCAPED
|
127
|
+
# #
|
128
|
+
# require 'hanami/helpers'
|
129
|
+
#
|
130
|
+
# class MyView
|
131
|
+
# include Hanami::Helpers
|
132
|
+
#
|
133
|
+
# def evil_attribute
|
134
|
+
# user_input_attribute = %(" onclick="javascript:alert('xss')" id=")
|
135
|
+
#
|
136
|
+
# html.span id: 'greet', class: ha(user_input_attribute) do
|
137
|
+
# "hello"
|
138
|
+
# end
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# view = MyView.new
|
143
|
+
# view.evil_attribute
|
144
|
+
# # => %(<span class="" onclick="javascript:alert('xss')" id="">hello</span>
|
145
|
+
def escape_html_attribute(input)
|
146
|
+
Utils::Escape.html_attribute(input)
|
147
|
+
end
|
148
|
+
|
149
|
+
# @since 0.1.0
|
150
|
+
alias_method :ha, :escape_html_attribute
|
151
|
+
|
152
|
+
# Escape an URL to be used in HTML attributes
|
153
|
+
#
|
154
|
+
# This allows only URLs with whitelisted schemes to pass the filter.
|
155
|
+
# Everything else is stripped.
|
156
|
+
#
|
157
|
+
# Default schemes are:
|
158
|
+
#
|
159
|
+
# * http
|
160
|
+
# * https
|
161
|
+
# * mailto
|
162
|
+
#
|
163
|
+
# If you want to allow a different set of schemes, you should pass it as
|
164
|
+
# second argument.
|
165
|
+
#
|
166
|
+
# This should be used only for untrusted contents: user input.
|
167
|
+
#
|
168
|
+
# @param input [String] the input
|
169
|
+
# @param schemes [Array<String>] an optional array of whitelisted schemes
|
170
|
+
#
|
171
|
+
# @return [String] the escaped string
|
172
|
+
#
|
173
|
+
# @since 0.1.0
|
174
|
+
#
|
175
|
+
# @see Hanami::Utils::Escape.url
|
176
|
+
# @see Hanami::Utils::Escape::DEFAULT_URL_SCHEMES
|
177
|
+
#
|
178
|
+
# @example Basic usage
|
179
|
+
# require 'hanami/helpers/escape_helper'
|
180
|
+
#
|
181
|
+
# class MyView
|
182
|
+
# include Hanami::Helpers::EscapeHelper
|
183
|
+
#
|
184
|
+
# def good_url
|
185
|
+
# url = "http://hanamirb.org"
|
186
|
+
#
|
187
|
+
# %(<a href="#{ hu(url) }">Hanami</a>
|
188
|
+
# end
|
189
|
+
#
|
190
|
+
# def evil_url
|
191
|
+
# url = "javascript:alert('xss')"
|
192
|
+
#
|
193
|
+
# %(<a href="#{ hu(url) }">Evil</a>
|
194
|
+
# end
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# view = MyView.new
|
198
|
+
#
|
199
|
+
# view.good_url
|
200
|
+
# # => %(<a href="http://hanamirb.org">Hanami</a>)
|
201
|
+
#
|
202
|
+
# view.evil_url
|
203
|
+
# # => %(<a href="">Evil</a>)
|
204
|
+
#
|
205
|
+
# @example Custom schemes
|
206
|
+
# require 'hanami/helpers/escape_helper'
|
207
|
+
#
|
208
|
+
# class MyView
|
209
|
+
# include Hanami::Helpers::EscapeHelper
|
210
|
+
#
|
211
|
+
# def ftp_link
|
212
|
+
# schemes = ['ftp', 'ftps']
|
213
|
+
# url = 'ftps://ftp.example.org'
|
214
|
+
#
|
215
|
+
# %(<a href="#{ hu(url, schemes) }">FTP</a>
|
216
|
+
# end
|
217
|
+
# end
|
218
|
+
#
|
219
|
+
# view = MyView.new
|
220
|
+
#
|
221
|
+
# view.ftp_link
|
222
|
+
# # => %(<a href="ftps://ftp.example.org">FTP</a>)
|
223
|
+
def escape_url(input, schemes = Utils::Escape::DEFAULT_URL_SCHEMES)
|
224
|
+
Utils::Escape.url(input, schemes)
|
225
|
+
end
|
226
|
+
|
227
|
+
# @since 0.1.0
|
228
|
+
alias_method :hu, :escape_url
|
229
|
+
|
230
|
+
# Bypass escape.
|
231
|
+
#
|
232
|
+
# Please notice that this can be really dangerous.
|
233
|
+
# Use at your own peril.
|
234
|
+
#
|
235
|
+
# @param input [String] the input
|
236
|
+
#
|
237
|
+
# @return [Hanami::Utils::Escape::SafeString] the string marked as safe string
|
238
|
+
#
|
239
|
+
# @since 0.1.0
|
240
|
+
#
|
241
|
+
# @example
|
242
|
+
# require 'hanami/helpers/escape_helper'
|
243
|
+
#
|
244
|
+
# class MyView
|
245
|
+
# include Hanami::Helpers::EscapeHelper
|
246
|
+
#
|
247
|
+
# def good_content
|
248
|
+
# raw "<p>hello</p>"
|
249
|
+
# end
|
250
|
+
#
|
251
|
+
# def evil_content
|
252
|
+
# raw "<script>alert('xss')</script>"
|
253
|
+
# end
|
254
|
+
# end
|
255
|
+
#
|
256
|
+
# view = MyView.new
|
257
|
+
#
|
258
|
+
# view.good_content
|
259
|
+
# # => "<p>hello</p>"
|
260
|
+
#
|
261
|
+
# #
|
262
|
+
# # !!! WE HAVE OPENED OUR APPLICATION TO AN XSS ATTACK !!!
|
263
|
+
# #
|
264
|
+
# view.evil_content
|
265
|
+
# # => "<script>alert('xss')</script>"
|
266
|
+
def raw(input)
|
267
|
+
Utils::Escape::SafeString.new(input)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,424 @@
|
|
1
|
+
require 'hanami/helpers/form_helper/form_builder'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Helpers
|
5
|
+
# Form builder
|
6
|
+
#
|
7
|
+
# By including <tt>Hanami::Helpers::FormHelper</tt> it will inject one public method: <tt>form_for</tt>.
|
8
|
+
# This is a HTML5 form builder.
|
9
|
+
#
|
10
|
+
# To understand the general HTML5 builder syntax of this framework, please
|
11
|
+
# consider to have a look at <tt>Hanami::Helpers::HtmlHelper</tt> documentation.
|
12
|
+
#
|
13
|
+
# This builder is independent from any template engine.
|
14
|
+
# This was hard to achieve without a compromise: the form helper should be
|
15
|
+
# used in one output block in a template or as a method in a view (see the examples below).
|
16
|
+
#
|
17
|
+
# Features:
|
18
|
+
#
|
19
|
+
# * Support for complex markup without the need of concatenation
|
20
|
+
# * Auto closing HTML5 tags
|
21
|
+
# * Support for view local variables
|
22
|
+
# * Method override support (PUT/PATCH/DELETE HTTP verbs aren't understood by browsers)
|
23
|
+
# * Automatic generation of HTML attributes for inputs: <tt>id</tt>, <tt>name</tt>, <tt>value</tt>
|
24
|
+
# * Allow to override HTML attributes
|
25
|
+
# * Extract values from request params and fill <tt>value</tt> attributes
|
26
|
+
# * Automatic selection of current value for radio button and select inputs
|
27
|
+
# * Infinite nested fields
|
28
|
+
#
|
29
|
+
# Supported tags and inputs:
|
30
|
+
#
|
31
|
+
# * <tt>color_field</tt>
|
32
|
+
# * <tt>date_field</tt>
|
33
|
+
# * <tt>datetime_field</tt>
|
34
|
+
# * <tt>datetime_local_field</tt>
|
35
|
+
# * <tt>email_field</tt>
|
36
|
+
# * <tt>hidden_field</tt>
|
37
|
+
# * <tt>file_field</tt>
|
38
|
+
# * <tt>fields_for</tt>
|
39
|
+
# * <tt>form_for</tt>
|
40
|
+
# * <tt>label</tt>
|
41
|
+
# * <tt>text_area</tt>
|
42
|
+
# * <tt>text_field</tt>
|
43
|
+
# * <tt>password_field</tt>
|
44
|
+
# * <tt>radio_button</tt>
|
45
|
+
# * <tt>select</tt>
|
46
|
+
# * <tt>submit</tt>
|
47
|
+
#
|
48
|
+
# @since 0.2.0
|
49
|
+
#
|
50
|
+
# @see Hanami::Helpers::FormHelper#form_for
|
51
|
+
# @see Hanami::Helpers::HtmlHelper
|
52
|
+
#
|
53
|
+
# @example One output block (template)
|
54
|
+
# <%=
|
55
|
+
# form_for :book, routes.books_path do
|
56
|
+
# text_field :title
|
57
|
+
#
|
58
|
+
# submit 'Create'
|
59
|
+
# end
|
60
|
+
# %>
|
61
|
+
#
|
62
|
+
# @example Method (view)
|
63
|
+
# require 'hanami/helpers'
|
64
|
+
#
|
65
|
+
# class MyView
|
66
|
+
# include Hanami::Helpers::FormHelper
|
67
|
+
#
|
68
|
+
# def my_form
|
69
|
+
# form_for :book, routes.books_path do
|
70
|
+
# text_field :title
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# # Corresponding template:
|
76
|
+
# #
|
77
|
+
# # <%= my_form %>
|
78
|
+
module FormHelper
|
79
|
+
# Default HTTP method for form
|
80
|
+
#
|
81
|
+
# @since 0.2.0
|
82
|
+
# @api private
|
83
|
+
DEFAULT_METHOD = 'POST'.freeze
|
84
|
+
|
85
|
+
# Default charset
|
86
|
+
#
|
87
|
+
# @since 0.2.0
|
88
|
+
# @api private
|
89
|
+
DEFAULT_CHARSET = 'utf-8'.freeze
|
90
|
+
|
91
|
+
# CSRF Token session key
|
92
|
+
#
|
93
|
+
# This key is shared with <tt>hanamirb</tt>, <tt>hanami-controller</tt>.
|
94
|
+
#
|
95
|
+
# @since 0.2.0
|
96
|
+
# @api private
|
97
|
+
CSRF_TOKEN = :_csrf_token
|
98
|
+
|
99
|
+
# Form object
|
100
|
+
#
|
101
|
+
# @since 0.2.0
|
102
|
+
class Form
|
103
|
+
# @return [Symbol] the form name
|
104
|
+
#
|
105
|
+
# @since 0.2.0
|
106
|
+
# @api private
|
107
|
+
attr_reader :name
|
108
|
+
|
109
|
+
# @return [String] the form action
|
110
|
+
#
|
111
|
+
# @since 0.2.0
|
112
|
+
# @api private
|
113
|
+
attr_reader :url
|
114
|
+
|
115
|
+
# @return [::Hash] the form values
|
116
|
+
#
|
117
|
+
# @since 0.2.0
|
118
|
+
# @api private
|
119
|
+
attr_reader :values
|
120
|
+
|
121
|
+
# Initialize a form
|
122
|
+
#
|
123
|
+
# It accepts a set of values that are used in combination with request
|
124
|
+
# params to autofill <tt>value</tt> attributes for fields.
|
125
|
+
#
|
126
|
+
# The keys of this Hash, MUST correspond to the structure of the (nested)
|
127
|
+
# fields of the form.
|
128
|
+
#
|
129
|
+
# For a given input where the <tt>name</tt> is `book[title]`, Hanami will
|
130
|
+
# look for `:book` key in values.
|
131
|
+
#
|
132
|
+
# If the current params have the same key, it will be PREFERRED over the
|
133
|
+
# given values.
|
134
|
+
#
|
135
|
+
# For instance, if <tt>params.get('book.title')</tt> equals to
|
136
|
+
# <tt>"TDD"</tt> while <tt>values[:book].title</tt> returns
|
137
|
+
# <tt>"No test"</tt>, the first will win.
|
138
|
+
#
|
139
|
+
# @param name [Symbol] the name of the form
|
140
|
+
# @param url [String] the action of the form
|
141
|
+
# @param values [Hash,NilClass] a Hash of values to be used to autofill
|
142
|
+
# <tt>value</tt> attributes for fields
|
143
|
+
# @param attributes [Hash,NilClass] a Hash of attributes to pass to the
|
144
|
+
# <tt>form</tt> tag
|
145
|
+
#
|
146
|
+
# @since 0.2.0
|
147
|
+
#
|
148
|
+
# @example Pass A Value
|
149
|
+
# # Given the following view
|
150
|
+
#
|
151
|
+
# module Web::Views::Deliveries
|
152
|
+
# class Edit
|
153
|
+
# include Web::View
|
154
|
+
#
|
155
|
+
# def form
|
156
|
+
# Form.new(:delivery, routes.delivery_path(id: delivery.id),
|
157
|
+
# {delivery: delivery, customer: customer},
|
158
|
+
# {method: :patch})
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# # And the corresponding template:
|
164
|
+
#
|
165
|
+
# <%=
|
166
|
+
# form_for form do
|
167
|
+
# date_field :delivered_on
|
168
|
+
#
|
169
|
+
# fields_for :customer do
|
170
|
+
# text_field :name
|
171
|
+
#
|
172
|
+
# fields_for :address do
|
173
|
+
# # ...
|
174
|
+
# text_field :city
|
175
|
+
# end
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# submit 'Update'
|
179
|
+
# end
|
180
|
+
# %>
|
181
|
+
#
|
182
|
+
# # It will render:
|
183
|
+
# #
|
184
|
+
# # <form action="/deliveries/1" method="POST" accept-charset="utf-8">
|
185
|
+
# # <input type="hidden" name="_method" value="PATCH">
|
186
|
+
# #
|
187
|
+
# # # Value taken from delivery.delivered_on
|
188
|
+
# # <input type="date" name="delivery[delivered_on]" id="delivery-delivered-on" value="2015-05-27">
|
189
|
+
# #
|
190
|
+
# # # Value taken from customer.name
|
191
|
+
# # <input type="text" name="delivery[customer][name]" id="delivery-customer-name" value="Luca">
|
192
|
+
# #
|
193
|
+
# # # Value taken from customer.address.city
|
194
|
+
# # <input type="text" name="delivery[customer][address][city]" id="delivery-customer-address-city" value="Rome">
|
195
|
+
# #
|
196
|
+
# # <button type="submit">Update</button>
|
197
|
+
# # </form>
|
198
|
+
def initialize(name, url, values = {}, attributes = {})
|
199
|
+
@name = name
|
200
|
+
@url = url
|
201
|
+
@values = values
|
202
|
+
@attributes = attributes || {}
|
203
|
+
end
|
204
|
+
|
205
|
+
# Return the method specified by the given attributes or fall back to
|
206
|
+
# the default value
|
207
|
+
#
|
208
|
+
# @return [String] the method for the action
|
209
|
+
#
|
210
|
+
# @since 0.2.0
|
211
|
+
# @api private
|
212
|
+
#
|
213
|
+
# @see Hanami::Helpers::FormHelper::DEFAULT_METHOD
|
214
|
+
def verb
|
215
|
+
@attributes.fetch(:method, DEFAULT_METHOD)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Instantiate a HTML5 form builder
|
220
|
+
#
|
221
|
+
# @overload form_for(name, url, options, &blk)
|
222
|
+
# Use inline values
|
223
|
+
# @param name [Symbol] the toplevel name of the form, it's used to generate
|
224
|
+
# input names, ids, and to lookup params to fill values.
|
225
|
+
# @param url [String] the form action URL
|
226
|
+
# @param options [Hash] HTML attributes to pass to the form tag and form values
|
227
|
+
# @option options [Hash] :values An optional payload of objects to pass
|
228
|
+
# @param blk [Proc] A block that describes the contents of the form
|
229
|
+
#
|
230
|
+
# @overload form_for(form, attributes, &blk)
|
231
|
+
# Use Form
|
232
|
+
# @param form [Hanami::Helpers::FormHelper::Form] a form object
|
233
|
+
# @param attributes [Hash] HTML attributes to pass to the form tag and form values
|
234
|
+
# @param blk [Proc] A block that describes the contents of the form
|
235
|
+
#
|
236
|
+
# @return [Hanami::Helpers::FormHelper::FormBuilder] the form builder
|
237
|
+
#
|
238
|
+
# @since 0.2.0
|
239
|
+
#
|
240
|
+
# @see Hanami::Helpers::FormHelper
|
241
|
+
# @see Hanami::Helpers::FormHelper::Form
|
242
|
+
# @see Hanami::Helpers::FormHelper::FormBuilder
|
243
|
+
#
|
244
|
+
# @example Inline Values In Template
|
245
|
+
# <%=
|
246
|
+
# form_for :book, routes.books_path, class: 'form-horizontal' do
|
247
|
+
# div do
|
248
|
+
# label :title
|
249
|
+
# text_field :title, class: 'form-control'
|
250
|
+
# end
|
251
|
+
#
|
252
|
+
# submit 'Create'
|
253
|
+
# end
|
254
|
+
# %>
|
255
|
+
#
|
256
|
+
# Output:
|
257
|
+
# # <form action="/books" method="POST" accept-charset="utf-8" id="book-form" class="form-horizontal">
|
258
|
+
# # <div>
|
259
|
+
# # <label for="book-title">Title</label>
|
260
|
+
# # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
|
261
|
+
# # </div>
|
262
|
+
# #
|
263
|
+
# # <button type="submit">Create</button>
|
264
|
+
# # </form>
|
265
|
+
#
|
266
|
+
#
|
267
|
+
#
|
268
|
+
# @example Use In A View
|
269
|
+
#
|
270
|
+
# module Web::Views::Books
|
271
|
+
# class New
|
272
|
+
#
|
273
|
+
# def form
|
274
|
+
# form_for :book, routes.books_path, class: 'form-horizontal' do
|
275
|
+
# div do
|
276
|
+
# label :title
|
277
|
+
# text_field :title, class: 'form-control'
|
278
|
+
# end
|
279
|
+
#
|
280
|
+
# submit 'Create'
|
281
|
+
# end
|
282
|
+
# end
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# <%= form %>
|
286
|
+
#
|
287
|
+
# Output:
|
288
|
+
# # <form action="/books" method="POST" accept-charset="utf-8" id="book-form" class="form-horizontal">
|
289
|
+
# # <div>
|
290
|
+
# # <label for="book-title">Title</label>
|
291
|
+
# # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
|
292
|
+
# # </div>
|
293
|
+
# #
|
294
|
+
# # <button type="submit">Create</button>
|
295
|
+
# # </form>
|
296
|
+
#
|
297
|
+
# @example Share Code Between Views
|
298
|
+
#
|
299
|
+
# # Given the following views to create and update a resource
|
300
|
+
# module Web::Views::Books
|
301
|
+
# class New
|
302
|
+
# include Web::View
|
303
|
+
#
|
304
|
+
# def form
|
305
|
+
# Form.new(:book, routes.books_path)
|
306
|
+
# end
|
307
|
+
#
|
308
|
+
# def submit_label
|
309
|
+
# 'Create'
|
310
|
+
# end
|
311
|
+
# end
|
312
|
+
#
|
313
|
+
# class Edit
|
314
|
+
# include Web::View
|
315
|
+
#
|
316
|
+
# def form
|
317
|
+
# Form.new(:book, routes.book_path(id: book.id),
|
318
|
+
# {book: book}, {method: :patch})
|
319
|
+
# end
|
320
|
+
#
|
321
|
+
# def submit_label
|
322
|
+
# 'Update'
|
323
|
+
# end
|
324
|
+
# end
|
325
|
+
# end
|
326
|
+
#
|
327
|
+
# # The respective templates can be identical:
|
328
|
+
#
|
329
|
+
# ## books/new.html.erb
|
330
|
+
# <%= render partial: 'books/form' %>
|
331
|
+
#
|
332
|
+
# ## books/edit.html.erb
|
333
|
+
# <%= render partial: 'books/form' %>
|
334
|
+
#
|
335
|
+
# # While the partial can have the following markup:
|
336
|
+
#
|
337
|
+
# ## books/_form.html.erb
|
338
|
+
# <%=
|
339
|
+
# form_for form, class: 'form-horizontal' do
|
340
|
+
# div do
|
341
|
+
# label :title
|
342
|
+
# text_field :title, class: 'form-control'
|
343
|
+
# end
|
344
|
+
#
|
345
|
+
# submit submit_label
|
346
|
+
# end
|
347
|
+
# %>
|
348
|
+
#
|
349
|
+
# Output:
|
350
|
+
# # <form action="/books" method="POST" accept-charset="utf-8" id="book-form" class="form-horizontal">
|
351
|
+
# # <div>
|
352
|
+
# # <label for="book-title">Title</label>
|
353
|
+
# # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
|
354
|
+
# # </div>
|
355
|
+
# #
|
356
|
+
# # <button type="submit">Create</button>
|
357
|
+
# # </form>
|
358
|
+
#
|
359
|
+
# @example Method override
|
360
|
+
# <%=
|
361
|
+
# form_for :book, routes.book_path(id: book.id), method: :put do
|
362
|
+
# text_field :title
|
363
|
+
#
|
364
|
+
# submit 'Update'
|
365
|
+
# end
|
366
|
+
# %>
|
367
|
+
#
|
368
|
+
# Output:
|
369
|
+
# # <form action="/books/23" accept-charset="utf-8" id="book-form" method="POST">
|
370
|
+
# # <input type="hidden" name="_method" value="PUT">
|
371
|
+
# # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
|
372
|
+
# #
|
373
|
+
# # <button type="submit">Update</button>
|
374
|
+
# # </form>
|
375
|
+
#
|
376
|
+
# @example Nested fields
|
377
|
+
# <%=
|
378
|
+
# form_for :delivery, routes.deliveries_path do
|
379
|
+
# text_field :customer_name
|
380
|
+
#
|
381
|
+
# fields_for :address do
|
382
|
+
# text_field :city
|
383
|
+
# end
|
384
|
+
#
|
385
|
+
# submit 'Create'
|
386
|
+
# end
|
387
|
+
# %>
|
388
|
+
#
|
389
|
+
# Output:
|
390
|
+
# # <form action="/deliveries" accept-charset="utf-8" id="delivery-form" method="POST">
|
391
|
+
# # <input type="text" name="delivery[customer_name]" id="delivery-customer-name" value="">
|
392
|
+
# # <input type="text" name="delivery[address][city]" id="delivery-address-city" value="">
|
393
|
+
# #
|
394
|
+
# # <button type="submit">Create</button>
|
395
|
+
# # </form>
|
396
|
+
def form_for(name, url, options = {}, &blk)
|
397
|
+
form = if name.is_a?(Form)
|
398
|
+
options = url
|
399
|
+
name
|
400
|
+
else
|
401
|
+
Form.new(name, url, options.delete(:values))
|
402
|
+
end
|
403
|
+
|
404
|
+
attributes = { action: form.url, method: form.verb, :'accept-charset' => DEFAULT_CHARSET, id: "#{ form.name }-form" }.merge(options)
|
405
|
+
FormBuilder.new(form, attributes, self, &blk)
|
406
|
+
end
|
407
|
+
|
408
|
+
# Returns CSRF Protection Token stored in session.
|
409
|
+
#
|
410
|
+
# It returns <tt>nil</tt> if sessions aren't enabled or the value is missing.
|
411
|
+
#
|
412
|
+
# @return [String,NilClass] token, if present
|
413
|
+
#
|
414
|
+
# @since 0.2.0
|
415
|
+
def csrf_token
|
416
|
+
if defined?(session)
|
417
|
+
session[CSRF_TOKEN]
|
418
|
+
elsif defined?(locals) && locals[:session]
|
419
|
+
locals[:session][CSRF_TOKEN]
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|