forme 1.3.0 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f907e786f16f2b923a7b06def6a58680fc922d27
4
- data.tar.gz: 88e4c51dc087507766783ee18b45fac47cf8007d
3
+ metadata.gz: b392c21e8ea5b18c3b1c125b84c36379b40595bf
4
+ data.tar.gz: de4ef22b48745082a9661989dd745824ef75ea07
5
5
  SHA512:
6
- metadata.gz: d76cdda658389678ae596350be89a0f42c9e6210ec55bf28d9c7f1df8615b429c0e537475616f9e37e77027d6123012538aa0a997967484954e2c8d598b58247
7
- data.tar.gz: 283d18147c8ca75e75b000af73d4a90030f65b672ff242b6124473ff5ed626a0a5fc9ec7798db6e62a497d0557b17d79ca9adde1b60065df36e7b5cf285aa71e
6
+ metadata.gz: bfc76ba5baa373ce677fe2fc7f44703d2e8783b9752e667a356a454293b11efecabe4b03fab498b040ac7afd3e0bc10a09f463681f5b839fa7620a984ba7bc1b
7
+ data.tar.gz: 93b41b112be039b14e2368618e9bc00d8599d7935828fddc27870bdd1426199c58fbc6c63b4085c48368e87c717ed5b0f952d76d684143ef455e1d19854c078f
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ === 1.4.0 (2016-02-01)
2
+
3
+ * Ignore submit buttons when using the :readonly formatter (jeremyevans)
4
+
5
+ * Respect :formatter option for radioset and checkboxset inputs (jeremyevans)
6
+
7
+ * Add support for running with --enable-frozen-string-literal on ruby 2.3 (jeremyevans)
8
+
9
+ * Integrate with the Sequel association_pks plugin in the Sequel plugin (jeremyevans)
10
+
11
+ * Do not add required * to label if :label=>nil in the Sequel plugin (jeremyevans)
12
+
13
+ * Add forme/bs3 library for Bootstrap 3 support (kematzy, jeremyevans) (#12, #14)
14
+
15
+ * Support an :html option in the default formatter to override the HTML created (jeremyevans)
16
+
1
17
  === 1.3.0 (2015-04-17)
2
18
 
3
19
  * Support option groups in select, checkboxset, and radioset inputs via :optgroups option (jeremyevans)
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2015 Jeremy Evans
1
+ Copyright (c) 2011-2016 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
data/README.rdoc CHANGED
@@ -73,7 +73,6 @@ passing the appopriate option to +input+ or +inputs+:
73
73
 
74
74
  f.input(:name, :wrapper=>:p)
75
75
 
76
-
77
76
  = Installation
78
77
 
79
78
  gem install forme
@@ -842,6 +841,15 @@ You can mark a configuration as the default using:
842
841
 
843
842
  Forme.default_config = :mine
844
843
 
844
+ === Bootstrap 3 Support
845
+
846
+ Forme ships with support for Bootstrap 3 HTML formatting. This support is shipped in
847
+ it's own file, so if you don't use it, you don't pay the memory penalty for loading
848
+ it.
849
+
850
+ require 'forme/bs3'
851
+ Forme.default_config = :bs3
852
+
845
853
  = Other Similar Projects
846
854
 
847
855
  All of these have external dependencies:
data/Rakefile CHANGED
@@ -48,45 +48,16 @@ end
48
48
 
49
49
  ### Specs
50
50
 
51
- begin
52
- begin
53
- raise LoadError if ENV['RSPEC1']
54
- # RSpec 2+
55
- require "rspec/core/rake_task"
56
- spec_class = RSpec::Core::RakeTask
57
- spec_files_meth = :pattern=
58
- rescue LoadError
59
- # RSpec 1
60
- require "spec/rake/spectask"
61
- spec_class = Spec::Rake::SpecTask
62
- spec_files_meth = :spec_files=
63
- end
64
-
65
- spec = lambda do |name, files, d|
66
- lib_dir = File.join(File.dirname(File.expand_path(__FILE__)), 'lib')
67
- ENV['RUBYLIB'] ? (ENV['RUBYLIB'] += ":#{lib_dir}") : (ENV['RUBYLIB'] = lib_dir)
68
- desc d
69
- spec_class.new(name) do |t|
70
- t.send spec_files_meth, files
71
- t.spec_opts = ENV["#{NAME.upcase}_SPEC_OPTS"].split if ENV["#{NAME.upcase}_SPEC_OPTS"]
72
- end
73
- end
74
-
75
- spec_with_cov = lambda do |name, files, d|
76
- spec.call(name, files, d)
77
- desc "#{d} with coverage"
78
- task "#{name}_cov" do
79
- ENV['COVERAGE'] = '1'
80
- Rake::Task[name].invoke
81
- end
82
- end
83
-
84
- task :default => [:spec]
85
- spec_with_cov.call("spec", Dir["spec/*_spec.rb"], "Run specs")
86
- rescue LoadError
87
- task :default do
88
- puts "Must install rspec to run the default task (which runs specs)"
89
- end
51
+ desc "Run specs"
52
+ task :spec do
53
+ sh "#{FileUtils::RUBY} -rubygems -I lib -e 'ARGV.each{|f| require f}' ./spec/*_spec.rb"
54
+ end
55
+ task :default => :spec
56
+
57
+ desc "Run specs with coverage"
58
+ task :spec do
59
+ ENV['COVERAGE'] = '1'
60
+ Rake::Task['spec'].invoke
90
61
  end
91
62
 
92
63
  ### Other
data/lib/forme.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  require 'date'
2
4
  require 'bigdecimal'
3
5
 
@@ -18,7 +20,7 @@ module Forme
18
20
  end
19
21
 
20
22
  # Array of all supported transformer types.
21
- TRANSFORMER_TYPES = [:formatter, :serializer, :wrapper, :error_handler, :helper, :labeler, :inputs_wrapper]
23
+ TRANSFORMER_TYPES = [:formatter, :serializer, :wrapper, :error_handler, :helper, :labeler, :inputs_wrapper, :tag_wrapper, :set_wrapper]
22
24
 
23
25
  # Transformer symbols shared by wrapper and inputs_wrapper
24
26
  SHARED_WRAPPERS = [:tr, :table, :ol, :fieldset_ol]
@@ -37,6 +39,7 @@ module Forme
37
39
  CONFIGURATIONS[:default][t] = :default
38
40
  TRANSFORMERS[t] = {}
39
41
  end
42
+ CONFIGURATIONS[:default].delete(:set_wrapper)
40
43
 
41
44
  # Register a new transformer with this library. Arguments:
42
45
  # +type+ :: Transformer type symbol
@@ -91,7 +94,7 @@ module Forme
91
94
  case type
92
95
  when :inputs_wrapper
93
96
  yield
94
- when :labeler, :error_handler, :wrapper, :helper
97
+ when :labeler, :error_handler, :wrapper, :helper, :set_wrapper, :tag_wrapper
95
98
  args.first
96
99
  else
97
100
  raise Error, "No matching #{type}: #{trans_name.inspect}"
@@ -107,9 +110,10 @@ module Forme
107
110
  # default transformer for the receiver.
108
111
  # +nil+ :: Assume the default transformer for this receiver.
109
112
  # otherwise :: return +trans+ directly if it responds to +call+, and raise an +Error+ if not.
110
- def self.transformer(type, trans, default_opts)
113
+ def self.transformer(type, trans, default_opts=nil)
111
114
  case trans
112
115
  when Symbol
116
+ type = :wrapper if type == :set_wrapper || type == :tag_wrapper
113
117
  TRANSFORMERS[type][trans] || raise(Error, "invalid #{type}: #{trans.inspect} (valid #{type}s: #{TRANSFORMERS[type].keys.map(&:inspect).join(', ')})")
114
118
  when Hash
115
119
  if trans.has_key?(type)
@@ -120,7 +124,7 @@ module Forme
120
124
  transformer(type, nil, default_opts)
121
125
  end
122
126
  when nil
123
- transformer(type, default_opts[type], nil) if default_opts
127
+ transformer(type, default_opts[type]) if default_opts
124
128
  else
125
129
  if trans.respond_to?(:call)
126
130
  trans
data/lib/forme/bs3.rb ADDED
@@ -0,0 +1,387 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Forme
4
+ register_config(:bs3, :formatter=>:bs3, :inputs_wrapper=>:bs3, :wrapper=>:bs3, :error_handler=>:bs3, :serializer=>:bs3, :labeler=>:bs3, :tag_wrapper=>:bs3, :set_wrapper=>:div)
5
+
6
+ # BS3 Boostrap formatted error handler which adds a span tag
7
+ # with "help-block with-errors" classes for the error message.
8
+ #
9
+ # Uses [github.com/1000hz/bootstrap-validator] formatting.
10
+ #
11
+ # Note! The default "error" class on the input is being removed.
12
+ #
13
+ # Registered as :bs3.
14
+ class ErrorHandler::Bootstrap3 < ErrorHandler
15
+ Forme.register_transformer(:error_handler, :bs3, new)
16
+
17
+ # Return tag with error message span tag after it.
18
+ def call(tag, input)
19
+ if tag.is_a?(Tag)
20
+ tag.attr[:class] = tag.attr[:class].to_s.gsub(/\s*error\s*/,'')
21
+ tag.attr.delete(:class) if tag.attr[:class].to_s == ''
22
+ end
23
+ attr = input.opts[:error_attr]
24
+ attr = attr ? attr.dup : {}
25
+ Forme.attr_classes(attr, 'help-block with-errors')
26
+ return [tag] if input.opts[:skip_error_message]
27
+
28
+ case input.type
29
+ when :submit, :reset
30
+ [tag]
31
+ when :textarea
32
+ input.opts[:wrapper] = :bs3
33
+ if input.opts[:wrapper_attr]
34
+ Forme.attr_classes(input.opts[:wrapper_attr], 'has-error')
35
+ else
36
+ input.opts[:wrapper_attr] = { :class => 'has-error' }
37
+ end
38
+ [ tag, input.tag(:span, attr, input.opts[:error]) ]
39
+
40
+ when :select
41
+ input.opts[:wrapper] = :bs3
42
+ if input.opts[:wrapper_attr]
43
+ Forme.attr_classes(input.opts[:wrapper_attr], 'has-error')
44
+ else
45
+ input.opts[:wrapper_attr] = { :class => 'has-error' }
46
+ end
47
+ [ tag, input.tag(:span, attr, input.opts[:error]) ]
48
+
49
+ when :checkbox, :radio
50
+
51
+ input.opts[:wrapper] = :div
52
+ if input.opts[:wrapper_attr]
53
+ Forme.attr_classes(input.opts[:wrapper_attr], 'has-error')
54
+ else
55
+ input.opts[:wrapper_attr] = { :class => 'has-error' }
56
+ end
57
+
58
+ [
59
+ input.tag(:div, { :class=> input.type.to_s }, [tag] ),
60
+ input.tag(:span, attr, input.opts[:error])
61
+ ]
62
+ else
63
+ if input.opts[:wrapper_attr]
64
+ Forme.attr_classes(input.opts[:wrapper_attr], 'has-error')
65
+ else
66
+ input.opts[:wrapper_attr] = { :class => 'has-error' }
67
+ end
68
+ [tag, input.tag(:span, attr, input.opts[:error])]
69
+ end
70
+ end
71
+ end
72
+
73
+ class Formatter::Bs3 < Formatter
74
+ Forme.register_transformer(:formatter, :bs3, self)
75
+
76
+ private
77
+
78
+ # Copied to remove .error from class attrs
79
+ def normalize_options
80
+ copy_options_to_attributes(ATTRIBUTE_OPTIONS)
81
+ copy_boolean_options_to_attributes(ATTRIBUTE_BOOLEAN_OPTIONS)
82
+ handle_key_option
83
+
84
+ Forme.attr_classes(@attr, @opts[:class]) if @opts.has_key?(:class)
85
+ # Forme.attr_classes(@attr, 'error') if @opts[:error]
86
+
87
+ if data = opts[:data]
88
+ data.each do |k, v|
89
+ sym = :"data-#{k}"
90
+ @attr[sym] = v unless @attr.has_key?(sym)
91
+ end
92
+ end
93
+ end
94
+
95
+ def set_label
96
+ form._tag(:span, {:class=>'set-label'}, @opts[:set_label])
97
+ end
98
+
99
+ def _add_set_error(tags)
100
+ tags << input.tag(:span, {:class=>'help-block with-errors'}, @opts[:set_error])
101
+ end
102
+
103
+ def format_radioset
104
+ @opts[:wrapper_attr] ||= {}
105
+ klasses = 'radioset'
106
+ klasses = @opts[:error] || @opts[:set_error] ? "#{klasses} has-error" : klasses
107
+ Forme.attr_classes(@opts[:wrapper_attr], klasses)
108
+ super
109
+ end
110
+
111
+ def format_checkboxset
112
+ @opts[:wrapper_attr] ||= {}
113
+ klasses = 'checkboxset'
114
+ klasses = @opts[:error] || @opts[:set_error] ? "#{klasses} has-error" : klasses
115
+ Forme.attr_classes(@opts[:wrapper_attr], klasses)
116
+ super
117
+ end
118
+
119
+ def _format_set(type, tag_attrs={})
120
+ raise Error, "can't have radioset with no options" unless @opts[:optgroups] || @opts[:options]
121
+ key = @opts[:key]
122
+ name = @opts[:name]
123
+ id = @opts[:id]
124
+ if @opts[:error]
125
+ @opts[:set_error] = @opts.delete(:error)
126
+ end
127
+ if @opts[:label]
128
+ @opts[:set_label] = @opts.delete(:label)
129
+ end
130
+
131
+ tag_wrapper = Forme.transformer(:tag_wrapper, @opts.delete(:tag_wrapper), @input.form_opts) || :default
132
+ wrapper = @opts.fetch(:wrapper){@opts[:wrapper] = @input.form_opts[:set_wrapper] || @input.form_opts[:wrapper]}
133
+ wrapper = Forme.transformer(:wrapper, wrapper)
134
+
135
+ tags = process_select_optgroups(:_format_set_optgroup) do |label, value, sel, attrs|
136
+ value ||= label
137
+ r_opts = attrs.merge(tag_attrs).merge(:label=>label||value, :label_attr=>{:class=>:option}, :wrapper=>tag_wrapper)
138
+ r_opts[:value] ||= value if value
139
+ r_opts[:checked] ||= :checked if sel
140
+
141
+ if name
142
+ r_opts[:name] ||= name
143
+ end
144
+ if id
145
+ r_opts[:id] ||= "#{id}_#{value}"
146
+ end
147
+ if key
148
+ r_opts[:key] ||= key
149
+ r_opts[:key_id] ||= value
150
+ end
151
+
152
+ form._input(type, r_opts)
153
+ end
154
+
155
+ if @opts[:set_error]
156
+ _add_set_error(tags)
157
+ end
158
+
159
+ tags.unshift(form._tag(:label, {}, @opts[:set_label])) if @opts[:set_label]
160
+
161
+ tags
162
+ end
163
+
164
+ end
165
+
166
+ # Formatter that adds "readonly" for most input types,
167
+ # and disables select/radio/checkbox inputs.
168
+ #
169
+ # Registered as :bs3_readonly.
170
+ class Formatter::Bs3ReadOnly < Formatter
171
+ Forme.register_transformer(:formatter, :bs3_readonly, self)
172
+
173
+ private
174
+
175
+ # Disabled checkbox inputs.
176
+ def format_checkbox
177
+ @attr[:disabled] = :disabled
178
+ super
179
+ end
180
+
181
+ # Use a span with text instead of an input field.
182
+ def _format_input(type)
183
+ @attr[:readonly] = :readonly
184
+ super
185
+ end
186
+
187
+ # Disabled radio button inputs.
188
+ def format_radio
189
+ @attr[:disabled] = :disabled
190
+ super
191
+ end
192
+
193
+ # Use a span with text of the selected values instead of a select box.
194
+ def format_select
195
+ @attr[:disabled] = :disabled
196
+ super
197
+ end
198
+
199
+ # Use a span with text instead of a text area.
200
+ def format_textarea
201
+ @attr[:readonly] = :readonly
202
+ super
203
+ end
204
+ end
205
+
206
+ # Use a <fieldset> tag to wrap the inputs.
207
+ #
208
+ # Registered as :bs3.
209
+ class InputsWrapper::Bootstrap3
210
+ Forme.register_transformer(:inputs_wrapper, :bs3, new)
211
+
212
+ def call(form, opts, &block)
213
+ attr = opts[:attr] ? opts[:attr].dup : {}
214
+ Forme.attr_classes(attr, 'inputs')
215
+ if legend = opts[:legend]
216
+ form.tag(:fieldset, attr) do
217
+ form.emit(form.tag(:legend, opts[:legend_attr], legend))
218
+ yield
219
+ end
220
+ else
221
+ form.tag(:fieldset, attr, &Proc.new)
222
+ end
223
+ end
224
+ end
225
+
226
+ # Use a <table class="table"> tag to wrap the inputs.
227
+ #
228
+ # Registered as :bs3_table.
229
+ class InputsWrapper::Bs3Table
230
+ Forme.register_transformer(:inputs_wrapper, :bs3_table, new)
231
+
232
+ # Wrap the inputs in a <table> tag.
233
+ def call(form, opts, &block)
234
+ attr = opts[:attr] ? opts[:attr].dup : { :class=>'table table-bordered'}
235
+ form.tag(:table, attr) do
236
+ if legend = opts[:legend]
237
+ form.emit(form.tag(:caption, opts[:legend_attr], legend))
238
+ end
239
+
240
+ if (labels = opts[:labels]) && !labels.empty?
241
+ form.emit(form.tag(:tr, {}, labels.map{|l| form._tag(:th, {}, l)}))
242
+ end
243
+
244
+ yield
245
+ end
246
+ end
247
+ end
248
+
249
+ # Labeler that creates BS3 label tags referencing the the given tag's id
250
+ # using a +for+ attribute. Requires that all tags with labels have +id+ fields.
251
+ #
252
+ # Registered as :bs3.
253
+ class Labeler::Bootstrap3
254
+ Forme.register_transformer(:labeler, :bs3, new)
255
+
256
+ # Return an array with a label tag as the first entry and +tag+ as
257
+ # a second entry. If the +input+ has a :label_for option, use that,
258
+ # otherwise use the input's :id option. If neither the :id or
259
+ # :label_for option is used, the label created will not be
260
+ # associated with an input.
261
+ def call(tag, input)
262
+ unless id = input.opts[:id]
263
+ if key = input.opts[:key]
264
+ namespaces = input.form_opts[:namespace]
265
+ id = "#{namespaces.join('_')}#{'_' unless namespaces.empty?}#{key}"
266
+ if key_id = input.opts[:key_id]
267
+ id += "_#{key_id.to_s}"
268
+ end
269
+ end
270
+ end
271
+
272
+ label_attr = input.opts[:label_attr]
273
+ label_attr = label_attr ? label_attr.dup : {}
274
+
275
+ label_attr[:for] = label_attr[:for] === false ? nil : input.opts.fetch(:label_for, id)
276
+ label = input.opts[:label]
277
+ lpos = input.opts[:label_position] || ([:radio, :checkbox].include?(input.type) ? :after : :before)
278
+
279
+ case input.type
280
+ when :checkbox, :radio
281
+ label = if lpos == :before
282
+ [label, ' ', tag]
283
+ else
284
+ [tag, ' ', label]
285
+ end
286
+ input.tag(:label, label_attr, label)
287
+ when :submit
288
+ [tag]
289
+ else
290
+ label = input.tag(:label, label_attr, [input.opts[:label]])
291
+ if lpos == :after
292
+ [tag, ' ', label]
293
+ else
294
+ [label, ' ', tag]
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ # Wraps inputs with <div class="form-group">
301
+ class Wrapper::Bootstrap3 < Wrapper
302
+ # Wrap the input in the tag of the given type.
303
+
304
+ def call(tag, input)
305
+ attr = input.opts[:wrapper_attr] ? input.opts[:wrapper_attr].dup : { }
306
+ klass = attr[:class] ? attr[:class].split(' ').unshift('form-group').uniq : ['form-group']
307
+
308
+ case input.type
309
+ when :submit, :reset
310
+ klass.delete('form-group')
311
+ attr[:class] = klass.sort.uniq.join(' ').strip
312
+ attr.delete(:class) if attr[:class].empty?
313
+ [tag]
314
+ when :radio, :checkbox
315
+ klass.delete('form-group')
316
+ klass.unshift( input.type.to_s )
317
+ attr[:class] = klass.sort.uniq.join(' ').strip
318
+ [input.tag(:div, attr, tag)]
319
+ when :hidden
320
+ super
321
+ else
322
+ attr[:class] = klass.sort.uniq.join(' ').strip
323
+ [input.tag(:div, attr, [tag])]
324
+ end
325
+
326
+ end
327
+
328
+ Forme.register_transformer(:wrapper, :bs3, new)
329
+ end
330
+
331
+ # Serializer class that converts tags to BS3 bootstrap tags.
332
+ #
333
+ # Registered at :bs3.
334
+ class Serializer::Bootstrap3 < Serializer
335
+ Forme.register_transformer(:serializer, :bs3, new)
336
+
337
+ def call(tag)
338
+ # All textual <input>, <textarea>, and <select> elements with .form-control
339
+ case tag
340
+ when Tag
341
+ case tag.type
342
+ when :input
343
+ # default to <input type="text"...> if not set
344
+ tag.attr[:type] = :text if tag.attr[:type].nil?
345
+
346
+ case tag.attr[:type].to_sym
347
+ when :checkbox, :radio, :hidden
348
+ # .form-control class causes rendering problems, so remove if found
349
+ tag.attr[:class].gsub!(/\s*form-control\s*/,'') if tag.attr[:class]
350
+ tag.attr[:class] = nil if tag.attr[:class] && tag.attr[:class].empty?
351
+
352
+ when :file
353
+ tag.attr[:class] = nil unless tag.attr[:class] && tag.attr[:class].strip != ''
354
+
355
+ when :submit, :reset
356
+ klass = ['btn', 'btn-default']
357
+ if tag.attr[:class] && tag.attr[:class].strip != ''
358
+ tag.attr[:class].split(' ').each { |c| klass.push c }
359
+ end
360
+ tag.attr[:class] = klass.uniq
361
+ ['btn-primary','btn-success', 'btn-info', 'btn-warning','btn-danger',
362
+ 'btn-outline','btn-link'
363
+ ].each do |k|
364
+ tag.attr[:class].delete('btn-default') if tag.attr[:class].include?(k)
365
+ end
366
+ tag.attr[:class].join(' ')
367
+
368
+ else
369
+ klass = tag.attr[:class] ? "form-control #{tag.attr[:class].to_s}" : ''
370
+ tag.attr[:class] = "form-control #{klass.gsub(/\s*form-control\s*/,'')}".strip
371
+ end
372
+
373
+ return "<#{tag.type}#{attr_html(tag.attr)}/>"
374
+
375
+ when :textarea, :select
376
+ klass = tag.attr[:class] ? "form-control #{tag.attr[:class].to_s}" : ''
377
+ tag.attr[:class] = "form-control #{klass.gsub(/\s*form-control\s*/,'')}".strip
378
+ return "#{serialize_open(tag)}#{call(tag.children)}#{serialize_close(tag)}"
379
+ else
380
+ super
381
+ end
382
+ else
383
+ super
384
+ end
385
+ end
386
+ end
387
+ end