formular 0.2.1 → 0.2.2

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -4
  3. data/CHANGELOG.md +29 -2
  4. data/README.md +5 -4
  5. data/formular.gemspec +1 -1
  6. data/lib/formular/attributes.rb +10 -21
  7. data/lib/formular/builders/basic.rb +4 -3
  8. data/lib/formular/builders/bootstrap3.rb +2 -1
  9. data/lib/formular/builders/bootstrap4.rb +4 -4
  10. data/lib/formular/element.rb +54 -23
  11. data/lib/formular/element/bootstrap3.rb +40 -8
  12. data/lib/formular/element/bootstrap3/checkable_control.rb +5 -8
  13. data/lib/formular/element/bootstrap3/horizontal.rb +7 -7
  14. data/lib/formular/element/bootstrap3/input_group.rb +2 -2
  15. data/lib/formular/element/bootstrap4.rb +17 -8
  16. data/lib/formular/element/bootstrap4/checkable_control.rb +5 -4
  17. data/lib/formular/element/bootstrap4/custom_control.rb +8 -4
  18. data/lib/formular/element/bootstrap4/horizontal.rb +3 -3
  19. data/lib/formular/element/bootstrap4/input_group.rb +12 -0
  20. data/lib/formular/element/foundation6.rb +5 -5
  21. data/lib/formular/element/foundation6/checkable_control.rb +2 -4
  22. data/lib/formular/element/foundation6/input_group.rb +2 -2
  23. data/lib/formular/element/foundation6/{wrapped_control.rb → wrapped.rb} +4 -4
  24. data/lib/formular/element/modules/checkable.rb +10 -11
  25. data/lib/formular/element/modules/control.rb +12 -4
  26. data/lib/formular/element/modules/error.rb +6 -1
  27. data/lib/formular/element/modules/escape_value.rb +14 -0
  28. data/lib/formular/element/modules/hint.rb +5 -3
  29. data/lib/formular/element/modules/label.rb +5 -2
  30. data/lib/formular/element/modules/{wrapped_control.rb → wrapped.rb} +14 -13
  31. data/lib/formular/elements.rb +62 -19
  32. data/lib/formular/helper.rb +18 -4
  33. data/lib/formular/html_block.rb +1 -1
  34. data/lib/formular/html_escape.rb +19 -0
  35. data/lib/formular/path.rb +1 -6
  36. data/lib/formular/version.rb +1 -1
  37. metadata +16 -7
@@ -0,0 +1,14 @@
1
+ require 'formular/element/module'
2
+ require 'formular/html_escape'
3
+ module Formular
4
+ class Element
5
+ module Modules
6
+ # include this module in an element to automatically escape the html of the value attribute
7
+ module EscapeValue
8
+ include Formular::Element::Module
9
+ include HtmlEscape
10
+ process_option :value, :html_escape
11
+ end # module EscapeValue
12
+ end # module Modules
13
+ end # class Element
14
+ end # module Formular
@@ -1,17 +1,19 @@
1
1
  require 'formular/element/module'
2
+ require 'formular/html_escape'
2
3
  module Formular
3
4
  class Element
4
5
  module Modules
5
6
  # this module provides hints to a control when included.
6
7
  module Hint
7
8
  include Formular::Element::Module
9
+ include HtmlEscape
8
10
  add_option_keys :hint, :hint_options
9
11
 
10
12
  # options functionality (same as SimpleForm):
11
13
  # options[:hint] == String return the string
12
14
  module InstanceMethods
13
15
  def hint_text
14
- options[:hint] if has_hint?
16
+ html_escape(options[:hint]) if has_hint?
15
17
  end
16
18
 
17
19
  def has_hint?
@@ -22,12 +24,12 @@ module Formular
22
24
  def hint_id
23
25
  return hint_options[:id] if hint_options[:id]
24
26
 
25
- id = attributes[:id] || form_encoded_id
27
+ id = options[:id] || form_encoded_id
26
28
  "#{id}_hint" if id
27
29
  end
28
30
 
29
31
  def hint_options
30
- @hint_options ||= Attributes[options[:hint_options]]
32
+ @hint_options ||= options[:hint_options] || {}
31
33
  end
32
34
  end # module InstanceMethods
33
35
  end # module Hint
@@ -1,10 +1,12 @@
1
1
  require 'formular/element/module'
2
+ require 'formular/html_escape'
2
3
  module Formular
3
4
  class Element
4
5
  module Modules
5
6
  # this module provides label options and methods to a control when included.
6
7
  module Label
7
8
  include Formular::Element::Module
9
+ include HtmlEscape
8
10
  add_option_keys :label, :label_options
9
11
 
10
12
  # options functionality:
@@ -13,7 +15,8 @@ module Formular
13
15
  # label as an option, you wont get one rendered
14
16
  module InstanceMethods
15
17
  def label_text
16
- options[:label]
18
+ return if options[:label].nil? || options[:label] == false
19
+ html_escape(options[:label])
17
20
  end
18
21
 
19
22
  def has_label?
@@ -21,7 +24,7 @@ module Formular
21
24
  end
22
25
 
23
26
  def label_options
24
- @label_options ||= Attributes[options[:label_options]]
27
+ @label_options ||= options[:label_options] || {}
25
28
  end
26
29
  end # module InstanceMethods
27
30
  end # module Label
@@ -8,9 +8,9 @@ module Formular
8
8
  module Modules
9
9
  # include this module to enable an element to render the entire wrapped input
10
10
  # e.g. wrapper{label+control+hint+error}
11
- module WrappedControl
11
+ module Wrapped
12
12
  include Formular::Element::Module
13
- include Control
13
+ # include Control
14
14
  include Hint
15
15
  include Error
16
16
  include Label
@@ -32,15 +32,16 @@ module Formular
32
32
  module InstanceMethods
33
33
  def wrapper(&block)
34
34
  wrapper_element = has_errors? ? :error_wrapper : :wrapper
35
- builder.send(wrapper_element, Attributes[options[:wrapper_options]], &block)
35
+ builder.send(wrapper_element, wrapper_options, &block)
36
36
  end
37
37
 
38
38
  def label
39
39
  return '' unless has_label?
40
40
 
41
- label_options[:content] = label_text
42
- label_options[:labeled_control] = self
43
- builder.label(label_options).to_s
41
+ label_opts = label_options.dup
42
+ label_opts[:content] = label_text
43
+ label_opts[:labeled_control] = self
44
+ builder.label(label_opts).to_s
44
45
  end
45
46
 
46
47
  def error
@@ -52,22 +53,22 @@ module Formular
52
53
 
53
54
  def hint
54
55
  return '' unless has_hint?
55
-
56
- hint_options[:content] = hint_text
57
- hint_options[:id] ||= hint_id
58
- builder.hint(hint_options).to_s
56
+ hint_opts = hint_options.dup
57
+ hint_opts[:content] = hint_text
58
+ hint_opts[:id] = hint_id # FIXME: this should work like a standard set_default
59
+ builder.hint(hint_opts).to_s
59
60
  end
60
61
 
61
62
  private
62
63
  def error_options
63
- @error_options ||= Attributes[options[:error_options]]
64
+ @error_options ||= options[:error_options] || {}
64
65
  end
65
66
 
66
67
  def wrapper_options
67
- @wrapper_options ||= Attributes[options[:wrapper_options]]
68
+ @wrapper_options ||= options[:wrapper_options] || {}
68
69
  end
69
70
  end # module InstanceMethods
70
- end # module WrappedControl
71
+ end # module Wrapped
71
72
  end # module Modules
72
73
  end # class Element
73
74
  end # module Formular
@@ -1,20 +1,21 @@
1
1
  require 'formular/element'
2
2
  require 'formular/element/module'
3
3
  require 'formular/element/modules/container'
4
- require 'formular/element/modules/wrapped_control'
4
+ require 'formular/element/modules/wrapped'
5
5
  require 'formular/element/modules/control'
6
6
  require 'formular/element/modules/checkable'
7
7
  require 'formular/element/modules/error'
8
+ require 'formular/element/modules/escape_value'
9
+ require 'formular/html_escape'
8
10
 
9
11
  module Formular
10
12
  class Element
11
13
  # These three are really just provided for convenience when creating other elements
12
14
  Container = Class.new(Formular::Element) { include Formular::Element::Modules::Container }
13
15
  Control = Class.new(Formular::Element) { include Formular::Element::Modules::Control }
14
- WrappedControl = Class.new(Formular::Element) { include Formular::Element::Modules::WrappedControl }
16
+ Wrapped = Class.new(Formular::Element) { include Formular::Element::Modules::Wrapped }
15
17
 
16
18
  # define some base classes to build from or easily use elsewhere
17
- Option = Class.new(Container) { tag :option }
18
19
  OptGroup = Class.new(Container) { tag :optgroup }
19
20
  Fieldset = Class.new(Container) { tag :fieldset }
20
21
  Legend = Class.new(Container) { tag :legend }
@@ -23,6 +24,12 @@ module Formular
23
24
  Span = Class.new(Container) { tag :span }
24
25
  Small = Class.new(Container) { tag :small }
25
26
 
27
+ class Option < Container
28
+ tag :option
29
+ include Formular::Element::Modules::EscapeValue
30
+ end
31
+
32
+
26
33
  class Hidden < Control
27
34
  tag :input
28
35
  set_default :type, 'hidden'
@@ -76,17 +83,17 @@ module Formular
76
83
 
77
84
  # because this mutates attributes, we have to call this before rendering the start_tag
78
85
  def method_tag
79
- method = attributes[:method]
86
+ method = options[:method]
80
87
 
81
88
  case method
82
89
  when /^get$/ # must be case-insensitive, but can't use downcase as might be nil
83
- attributes[:method] = 'get'
90
+ options[:method] = 'get'
84
91
  ''
85
92
  when /^post$/, '', nil
86
- attributes[:method] = 'post'
93
+ options[:method] = 'post'
87
94
  ''
88
95
  else
89
- attributes[:method] = 'post'
96
+ options[:method] = 'post'
90
97
  Hidden.(value: method, name: '_method').to_s
91
98
  end
92
99
  end
@@ -140,12 +147,13 @@ module Formular
140
147
  # as per MDN A label element can have both a 'for' attribute and a contained control element,
141
148
  # as long as the for attribute points to the contained control element.
142
149
  def labeled_control_id
143
- return options[:labeled_control].attributes[:id] if options[:labeled_control]
150
+ return options[:labeled_control].options[:id] if options[:labeled_control]
144
151
  return builder.path(options[:attribute_name]).to_encoded_id if options[:attribute_name] && builder
145
152
  end
146
153
  end # class Label
147
154
 
148
155
  class Submit < Formular::Element
156
+ include Formular::Element::Modules::EscapeValue
149
157
  tag :input
150
158
 
151
159
  set_default :type, 'submit'
@@ -153,26 +161,31 @@ module Formular
153
161
  html { closed_start_tag }
154
162
  end # class Submit
155
163
 
156
- class Button < Formular::Element::Container
157
- tag :button
158
- add_option_keys :value
164
+ class Button < Container
165
+ include Formular::Element::Modules::Control
159
166
 
160
- def content
161
- options[:value] || super
162
- end
167
+ tag :button
163
168
  end # class Button
164
169
 
165
170
  class Input < Control
171
+ include HtmlEscape
172
+
166
173
  tag :input
167
174
  set_default :type, 'text'
175
+ process_option :value, :html_escape
176
+
168
177
  html { closed_start_tag }
169
178
  end # class Input
170
179
 
171
180
  class Select < Control
172
181
  include Formular::Element::Modules::Collection
182
+ include HtmlEscape
183
+
173
184
  tag :select
174
185
 
175
- add_option_keys :value
186
+ add_option_keys :value, :prompt, :include_blank
187
+ process_option :collection, :inject_placeholder
188
+ process_option :name, :name_array_if_multiple
176
189
 
177
190
  html do |input|
178
191
  concat start_tag
@@ -203,6 +216,32 @@ module Formular
203
216
  end
204
217
 
205
218
  private
219
+ # only append the [] to name if the multiple option is set
220
+ def name_array_if_multiple(name)
221
+ return unless name
222
+
223
+ options[:multiple] ? "#{name}[]" : name
224
+ end
225
+
226
+ # same handling as simple form
227
+ # prompt: a nil value option appears if we have no selected option
228
+ # include blank: includes our nil value option regardless (useful for optional fields)
229
+ def inject_placeholder(collection)
230
+ placeholder = if options[:include_blank]
231
+ placeholder_option(options[:include_blank])
232
+ elsif options[:prompt] && options[:value].nil?
233
+ placeholder_option(options[:prompt])
234
+ end
235
+
236
+ collection.unshift(placeholder) if placeholder
237
+
238
+ collection
239
+ end
240
+
241
+ def placeholder_option(value)
242
+ text = value.is_a?(String) ? html_escape(value) : ""
243
+ [text, ""]
244
+ end
206
245
 
207
246
  def collection_to_options(collection)
208
247
  collection.map do |item|
@@ -225,7 +264,7 @@ module Formular
225
264
 
226
265
  opts[:value] = item.send(options[:value_method])
227
266
  opts[:content] = item.send(options[:label_method])
228
- opts[:selected] = 'selected' if opts[:value] == options[:value]
267
+ opts[:selected] = 'selected' if opts[:value].to_s == options[:value].to_s
229
268
 
230
269
  Formular::Element::Option.new(opts).to_s
231
270
  end
@@ -234,11 +273,11 @@ module Formular
234
273
  class Checkbox < Control
235
274
  tag :input
236
275
 
237
- add_option_keys :unchecked_value, :include_hidden, :multiple
276
+ add_option_keys :unchecked_value, :checked_value, :include_hidden, :multiple
238
277
 
239
278
  set_default :type, 'checkbox'
240
279
  set_default :unchecked_value, :default_unchecked_value
241
- set_default :value, '1' # instead of reader value
280
+ set_default :value, :default_checked_value # instead of reader value
242
281
  set_default :include_hidden, true
243
282
 
244
283
  include Formular::Element::Modules::Checkable
@@ -260,11 +299,15 @@ module Formular
260
299
  def hidden_tag
261
300
  return '' unless options[:include_hidden]
262
301
 
263
- Hidden.(value: options[:unchecked_value], name: attributes[:name]).to_s
302
+ Hidden.(value: options[:unchecked_value], name: options[:name]).to_s
264
303
  end
265
304
 
266
305
  private
267
306
 
307
+ def default_checked_value
308
+ options[:checked_value] || '1'
309
+ end
310
+
268
311
  def default_unchecked_value
269
312
  collection? ? '' : '0'
270
313
  end
@@ -21,23 +21,37 @@ module Formular
21
21
  }.freeze
22
22
 
23
23
  class << self
24
+ def _builder
25
+ @builder || :basic
26
+ end
24
27
  attr_writer :builder
25
28
 
26
- def builder(name = nil)
27
- name ||= :basic
29
+ def builder(name)
30
+ self.builder = name
31
+ end
32
+
33
+ def load_builder(name)
34
+ builder_const = BUILDERS.fetch(name, nil)
35
+ return name unless builder_const
36
+
28
37
  require "formular/builders/#{name}"
29
- self.builder = Formular::Builders.const_get(BUILDERS.fetch(name)) # Formular::Builders::Bootstrap3
38
+ Formular::Builders.const_get(builder_const)
30
39
  end
31
40
  end
32
41
 
33
42
  private
34
43
 
35
44
  def builder(model, **options)
36
- builder = Formular::Helper.builder(options.delete(:builder))
45
+ builder_name = options.delete(:builder)
46
+ builder_name ||= Formular::Helper._builder
47
+
48
+ builder = Formular::Helper.load_builder(builder_name)
49
+
37
50
  options[:model] ||= model
38
51
 
39
52
  builder.new(options)
40
53
  end
54
+
41
55
  end # module Helper
42
56
 
43
57
  module RailsHelper
@@ -35,7 +35,7 @@ module Formular
35
35
 
36
36
  # return a closed start tag (e.g. <input name="body"/>)
37
37
  def closed_start_tag
38
- start_tag.gsub('>', '/>')
38
+ start_tag.gsub(/\>$/, '/>')
39
39
  end
40
40
 
41
41
  # returns the end/ closing tag for an element
@@ -0,0 +1,19 @@
1
+ module Formular
2
+ module HtmlEscape
3
+ # see activesupport/lib/active_support/core_ext/string/output_safety.rb
4
+
5
+ HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
6
+ HTML_ESCAPE_REGEXP = /[&"'><]/
7
+ HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
8
+
9
+ # A utility method for escaping HTML tag characters.
10
+ def html_escape(string)
11
+ string.to_s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
12
+ end
13
+
14
+ # A utility method for escaping HTML without affecting existing escaped entities.
15
+ def html_escape_once(string)
16
+ string.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
17
+ end
18
+ end
19
+ end
@@ -3,9 +3,6 @@ module Formular
3
3
  # name #attribute without model...
4
4
  # [User, :name] => user[name] #regular attribute
5
5
  # [User, roles: 0, :name] => user[roles][][name]
6
-
7
- # DISCUSS: Should we also enable the following for rails accepts_nested_attributes support
8
- # [User, roles: 0, :name => user[roles_attributes][0][name]
9
6
  def to_encoded_name
10
7
  map.with_index do |segment, i|
11
8
  first_or_last = i == 0 || i == size
@@ -20,9 +17,7 @@ module Formular
20
17
 
21
18
  # need to inject the index in here... else we will end up with the same ids
22
19
  # [User, :name] => user_name #regular attribute
23
- # [User, roles: 0, :name] => user_roles_0_name #nested not rails
24
- # DISCUSS: Should we also enable the following for rails accepts_nested_attributes support
25
- # [User, roles: 0, :name => user_roles_attributes_0_name #nested rails
20
+ # [User, roles: 0, :name] => user_roles_0_name
26
21
  def to_encoded_id
27
22
  map { |segment| segment.is_a?(Array) ? segment.join('_') : segment }.join('_')
28
23
  end
@@ -1,3 +1,3 @@
1
1
  module Formular
2
- VERSION = '0.2.1'
2
+ VERSION = '0.2.2'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: formular
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2016-09-29 00:00:00.000000000 Z
12
+ date: 2017-09-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: declarative
@@ -29,16 +29,22 @@ dependencies:
29
29
  name: uber
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - "~>"
32
+ - - ">="
33
33
  - !ruby/object:Gem::Version
34
34
  version: 0.0.11
35
+ - - "<"
36
+ - !ruby/object:Gem::Version
37
+ version: 0.2.0
35
38
  type: :runtime
36
39
  prerelease: false
37
40
  version_requirements: !ruby/object:Gem::Requirement
38
41
  requirements:
39
- - - "~>"
42
+ - - ">="
40
43
  - !ruby/object:Gem::Version
41
44
  version: 0.0.11
45
+ - - "<"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.2.0
42
48
  - !ruby/object:Gem::Dependency
43
49
  name: bundler
44
50
  requirement: !ruby/object:Gem::Requirement
@@ -174,22 +180,25 @@ files:
174
180
  - lib/formular/element/bootstrap4/checkable_control.rb
175
181
  - lib/formular/element/bootstrap4/custom_control.rb
176
182
  - lib/formular/element/bootstrap4/horizontal.rb
183
+ - lib/formular/element/bootstrap4/input_group.rb
177
184
  - lib/formular/element/foundation6.rb
178
185
  - lib/formular/element/foundation6/checkable_control.rb
179
186
  - lib/formular/element/foundation6/input_group.rb
180
- - lib/formular/element/foundation6/wrapped_control.rb
187
+ - lib/formular/element/foundation6/wrapped.rb
181
188
  - lib/formular/element/module.rb
182
189
  - lib/formular/element/modules/checkable.rb
183
190
  - lib/formular/element/modules/collection.rb
184
191
  - lib/formular/element/modules/container.rb
185
192
  - lib/formular/element/modules/control.rb
186
193
  - lib/formular/element/modules/error.rb
194
+ - lib/formular/element/modules/escape_value.rb
187
195
  - lib/formular/element/modules/hint.rb
188
196
  - lib/formular/element/modules/label.rb
189
- - lib/formular/element/modules/wrapped_control.rb
197
+ - lib/formular/element/modules/wrapped.rb
190
198
  - lib/formular/elements.rb
191
199
  - lib/formular/helper.rb
192
200
  - lib/formular/html_block.rb
201
+ - lib/formular/html_escape.rb
193
202
  - lib/formular/path.rb
194
203
  - lib/formular/version.rb
195
204
  homepage: http://trailblazer.to/gems/formular.html
@@ -212,7 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
212
221
  version: '0'
213
222
  requirements: []
214
223
  rubyforge_project:
215
- rubygems_version: 2.4.6
224
+ rubygems_version: 2.5.1
216
225
  signing_key:
217
226
  specification_version: 4
218
227
  summary: Form builder based on Cells. Fast, Furious, and Framework-Agnostic.