formular 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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.