forme 1.3.0 → 1.4.0

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