Class: Satis::Forms::Builder

Inherits:
ActionView::Helpers::FormBuilder
  • Object
show all
Includes:
Concerns::Buttons, Concerns::File, Concerns::Options, Concerns::Required, Concerns::Select
Defined in:
lib/satis/forms/builder.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#assocationObject (readonly)

Returns the value of attribute assocation.



14
15
16
# File 'lib/satis/forms/builder.rb', line 14

def assocation
  @assocation
end

#templateObject (readonly)

Returns the value of attribute template.



14
15
16
# File 'lib/satis/forms/builder.rb', line 14

def template
  @template
end

Instance Method Details

#association(name, options, &block) ⇒ Object

Simple-form like association



40
41
42
43
44
45
46
47
48
49
# File 'lib/satis/forms/builder.rb', line 40

def association(name, options, &block)
  @form_options = options

  @association = name
  reflection = @object.class.reflections[name.to_s]

  method = reflection.join_foreign_key

  send(input_type_for(method, options), method, options, &block)
end

#boolean_input(method, options = {}) ⇒ Object



302
303
304
305
306
307
308
309
310
311
# File 'lib/satis/forms/builder.rb', line 302

def boolean_input(method, options = {})
  form_group(method, options) do
    tag.div(class: 'custom-control custom-checkbox') do
      safe_join [
        check_box(method, merge_input_options({ class: 'custom-control-input' }, options[:input_html])),
        label(method, options[:label], class: 'custom-control-label')
      ]
    end
  end
end

#check_boxes_input(method, options = {}) ⇒ Object



390
391
392
# File 'lib/satis/forms/builder.rb', line 390

def check_boxes_input(method, options = {})
  collection_of(:check_boxes, method, options)
end

#collection_of(input_type, method, options = {}) ⇒ Object



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/satis/forms/builder.rb', line 359

def collection_of(input_type, method, options = {})
  form_builder_method, custom_class, input_builder_method = case input_type
                                                            when :radio_buttons then [:collection_radio_buttons,
                                                                                      'custom-radio', :radio_button]
                                                            when :check_boxes then [:collection_check_boxes,
                                                                                    'custom-checkbox', :check_box]
                                                            else raise 'Invalid input_type for collection_of, valid input_types are ":radio_buttons", ":check_boxes"'
                                                            end
  options[:value_method] ||= :last
  options[:text_method]  ||= options[:label_method] || :first
  form_group(method, options) do
    safe_join [
      label(method, options[:label]),
      tag.br,
      (send(form_builder_method, method, options[:collection], options[:value_method],
            options[:text_method]) do |b|
         tag.div(class: "custom-control #{custom_class}") do
           safe_join [
             b.send(input_builder_method, options.fetch(:input_html, {}).merge(class: 'custom-control-input')),
             b.label(class: 'custom-control-label')
           ]
         end
       end)
    ]
  end
end

#custom_label(method, title, options = {}) ⇒ Object



205
206
207
208
209
210
211
212
213
214
215
# File 'lib/satis/forms/builder.rb', line 205

def custom_label(method, title, options = {})
  all_classes = "#{options[:class]} form-label".strip
  label(method, title, class: all_classes, data: options[:data]) do |translation|
    safe_join [
      tag.span(title || translation, class: required?(method) ? 'required' : ''),
      ' ',
      required(method, options),
      help(method, options)
    ]
  end
end

#date_time_input(method, options = {}, &block) ⇒ Object

wrapper_html: { data: { ‘date-time-picker-time-picker’: ‘true’, controller: ‘date-time-picker’, ‘date-time-picker-start-date’ => (@holiday.start_at || params&.[](:start_at) && Time.parse(params[:start_at]) || Time.current)&.iso8601 } }



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/satis/forms/builder.rb', line 323

def date_time_input(method, options = {}, &block)
  case object_type_for_method(method)
  when :date
    options[:time_picker] = options.key?(:time_picker) ? options[:time_picker] : false
  when :datetime
    options[:time_picker] = options.key?(:time_picker) ? options[:time_picker] : true
  end
  form_group(method, options) do
    safe_join [
      (custom_label(method, options[:label], options) unless options[:label] == false),
      render(Satis::DateTimePicker::Component.new(form: self, attribute: method, title: options[:label], **options,
    &block))
    ]
  end
end

#editor(method, options = {}, &block) ⇒ Object

A codemirror editor, backed by a text-area



33
34
35
36
37
# File 'lib/satis/forms/builder.rb', line 33

def editor(method, options = {}, &block)
  @form_options = options

  editor_input(method, options, &block)
end

#editor_input(method, options = {}) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/satis/forms/builder.rb', line 277

def editor_input(method, options = {})
  form_group(method, options) do
    safe_join [
      (custom_label(method, options[:label], options) unless options[:label] == false),
      tag.div(text_area(method,
                        merge_input_options({
                                              class: 'form-control',
                                              data: {
                                                controller: 'satis-editor',
                                                'satis-editor-target' => 'textarea',
                                                'satis-editor-read-only-value' => options.delete(:read_only) || false,
                                                'satis-editor-mode-value' => options.delete(:mode) || 'text/html',
                                                'satis-editor-height-value' => options.delete(:height) || '200px',
                                                'satis-editor-color-scheme-value' => options.delete(:color_scheme),
                                                'satis-editor-color-scheme-dark-value' => options.delete(:color_scheme_dark) || 'lucario'
                                              }

                                            }, options[:input_html])), class: "editor #{if has_error?(method)
                                                                                          'is-invalid'
                                                                                        end}"),
      hint_text(options[:hint] || '⌘-F/⌃-f: search; ⌥-g: goto line, ⌃-space: autocomplete')
    ]
  end
end

#error_text(method) ⇒ Object

FIXME: These don’t work for relations or location_id, error is on location When using the association helper, we need to set a @assocation variable any other input should clear it



180
181
182
183
184
185
186
187
188
# File 'lib/satis/forms/builder.rb', line 180

def error_text(method)
  return if !has_error?(method) && !has_error?(method.to_s.gsub(/_id$/, ''))

  all_errors = @object.errors[method].dup
  all_errors += @object.errors[method.to_s.gsub(/_id$/, '')] if method.to_s.ends_with?('_id')

  tag.div(all_errors.uniq.join('<br />').html_safe,
          class: 'invalid-feedback')
end

#fields_for(*args, &block) ⇒ Object

Wrapper around fields_for, using Satis::Forms::Builder Example:

form_for @user do |f|
  f.simple_fields_for :printers do |printers_form|
    # Here you have all satis' form methods available
    printers_form.input :name
  end
end


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/satis/forms/builder.rb', line 62

def fields_for(*args, &block)
  options = args.extract_options!
  name = args.first
  template_object = args.second

  # FIXME: Yuk - is it possible to detect when this should not be allowed?
  # Like checking for whether destroy is allowed on assocations?
  allow_actions = options.key?(:allow_actions) ? options[:allow_actions] : true
  show_actions = @object.respond_to?("#{name}_attributes=") && @object.send(name).respond_to?(:each) && template_object && allow_actions == true

  html_options = options[:html] || {}

  html_options[:data] ||= {}
  html_options[:data] = flatten_hash(html_options[:data])
  html_options[:data][:controller] =
    ['satis-fields-for'].concat(options[:html]&.[](:data)&.[](:controller).to_s.split).join(' ')
  html_options[:class] = ['fields_for'].concat(options[:html]&.[](:class).to_s.split).join(' ')

  options[:builder] ||= if self.class < ActionView::Helpers::FormBuilder
                          self.class
                        else
                          Satis::Forms::Builder
                        end

  # Only do the whole nested-form thing with a collection
  if show_actions
    view_options = {
      form: self,
      collection: name,
      template_object: template_object,
      options: options
    }
    tag.div(**html_options) do
      render 'shared/fields_for', view_options, &block
    end

    # FIXME: You would want to do this:
    # render(Satis::FieldsFor::Component.new(
    #          form: self, name: name, template_object: template_object, **options, &block
    #        ))
  else
    invalid_feedback = nil
    if @object.errors.messages[name].present?
      invalid_feedback = tag.div(@object.errors.messages[name].join(', '),
                                 class: 'invalid-feedback')
    end
    safe_join [
      invalid_feedback,
      rails_fields_for(*args, options, &block)
    ].compact
  end
end

#flatten_hash(hash) ⇒ Object



427
428
429
430
431
432
433
434
435
436
437
# File 'lib/satis/forms/builder.rb', line 427

def flatten_hash(hash)
  hash.each_with_object({}) do |(k, v), h|
    if v.is_a? Hash
      flatten_hash(v).map do |h_k, h_v|
        h["#{k}_#{h_k}".to_sym] = h_v
      end
    else
      h[k] = v
    end
  end
end

#form_group(method, options = {}, &block) ⇒ Object



161
162
163
164
165
166
167
168
169
# File 'lib/satis/forms/builder.rb', line 161

def form_group(method, options = {}, &block)
  tag.div(class: "form-group form-group-#{method}", data: options[:data]) do
    safe_join [
      block.call,
      hint_text(options[:hint]),
      error_text(method)
    ].compact
  end
end

#has_error?(method) ⇒ Boolean

Returns:

  • (Boolean)


199
200
201
202
203
# File 'lib/satis/forms/builder.rb', line 199

def has_error?(method)
  return false unless @object.respond_to?(:errors)

  @object.errors.key?(method)
end

#help(method, options = {}) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/satis/forms/builder.rb', line 223

def help(method, options = {})
  text = if options[:help].present?
           options[:help]
         else
           Satis.config.default_help_text(@template, @object, method,
                                          @options[:help_scope] || options[:help_scope])
         end

  return if text.blank?

  tag.i(class: 'fal fa-circle-info', 'data-controller': 'help', 'data-help-content-value': text)
end

#hidden(method, options = {}, &block) ⇒ Object

A hidden input



133
134
135
136
137
# File 'lib/satis/forms/builder.rb', line 133

def hidden(method, options = {}, &block)
  @form_options = options

  hidden_input(method, options, &block)
end

#hidden_input(method, options = {}) ⇒ Object



236
237
238
# File 'lib/satis/forms/builder.rb', line 236

def hidden_input(method, options = {})
  hidden_field(method, options[:input_html] || {})
end

#hint_text(text) ⇒ Object



171
172
173
174
175
# File 'lib/satis/forms/builder.rb', line 171

def hint_text(text)
  return if text.nil?

  tag.small text, class: 'form-text text-muted'
end

#input(method, options = {}, &block) ⇒ Object

Regular input



23
24
25
26
27
28
29
30
# File 'lib/satis/forms/builder.rb', line 23

def input(method, options = {}, &block)
  @form_options = options

  options[:input_html] ||= {}
  options[:input_html][:disabled] = options.delete(:disabled)

  send(input_type_for(method, options), method, options, &block)
end

#input_type_for(method, options) ⇒ Object

Non public



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/satis/forms/builder.rb', line 141

def input_type_for(method, options)
  object_type = object_type_for_method(method)
  input_type = case object_type
               when :date then :date_time
               when :datetime then :date_time
               when :integer then :string
               when :float then :string
               else object_type
               end
  override_input_type = if options[:as]
                          options[:as]
                        elsif options[:collection]
                          :select
                        elsif options[:url]
                          :dropdown
                        end

  "#{override_input_type || input_type}_input"
end

#merge_input_options(options, user_options) ⇒ Object



420
421
422
423
424
425
# File 'lib/satis/forms/builder.rb', line 420

def merge_input_options(options, user_options)
  return options if user_options.nil?

  # TODO: handle class merging here
  options.merge(user_options)
end

#number_input(method, options = {}) ⇒ Object



253
254
255
256
257
258
259
260
261
262
263
# File 'lib/satis/forms/builder.rb', line 253

def number_input(method, options = {})
  form_group(method, options) do
    safe_join [
      (custom_label(method, options[:label], options) unless options[:label] == false),
      number_field(method,
                   merge_input_options({ class: "form-control #{if has_error?(method)
                                                                  'is-invalid'
                                                                end}" }, options[:input_html]))
    ]
  end
end

#object_type_for_method(method) ⇒ Object



190
191
192
193
194
195
196
197
# File 'lib/satis/forms/builder.rb', line 190

def object_type_for_method(method)
  result = if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(method)
             @object.type_for_attribute(method.to_s).try(:type)
           elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(method)
             @object.column_for_attribute(method).try(:type)
           end
  result || :string
end

#phone_input(method, options = {}) ⇒ Object



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/satis/forms/builder.rb', line 339

def phone_input(method, options = {})
  # options[:input_html] = {}

  # options[:input_html] = { 'data-controller' => 'phone-number',
  #                          'data-phone-number-target': 'input',
  #                          'data-action': 'keyup->phone-number#change blur->phone-number#change' }

  tag.div('data-controller' => 'phone-number') do
    safe_join [
      hidden_field(method,
                   merge_input_options({ class: 'form-control', 'data-phone-number-target': 'hiddenInput' },
                                       options[:input_html])),
      @template.text_field_tag('dummy', @object.try(method), class: "form-control #{if has_error?(method)
                                                                                      'is-invalid'
                                                                                    end}", 'data-phone-number-target': 'input',
                                                             'data-action': 'input->phone-number#change')
    ]
  end
end

#radio_buttons_input(method, options = {}) ⇒ Object



386
387
388
# File 'lib/satis/forms/builder.rb', line 386

def radio_buttons_input(method, options = {})
  collection_of(:radio_buttons, method, options)
end

#rails_fields_forObject



51
# File 'lib/satis/forms/builder.rb', line 51

alias rails_fields_for fields_for

#required(method, _options = {}) ⇒ Object



217
218
219
220
221
# File 'lib/satis/forms/builder.rb', line 217

def required(method, _options = {})
  return unless required?(method)

  tag.i(class: 'fas fa-hexagon-exclamation')
end

#rich_text(*args) ⇒ Object



115
116
117
118
119
120
121
122
123
# File 'lib/satis/forms/builder.rb', line 115

def rich_text(*args)
  options = args.extract_options!
  form_group(*args, options) do
    safe_join [
      (custom_label(*args, options[:label]) unless options[:label] == false),
      rich_text_area(*args, options)
    ]
  end
end

#string_field(method, options = {}) ⇒ Object



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/satis/forms/builder.rb', line 394

def string_field(method, options = {})
  # if specifically set to string, no more magic.
  return text_field(method, options) if options[:as] == :string

  case options[:as] || object_type_for_method(method)
  when :date then text_field(method, options)
  when :datetime then text_field(method, options)
  when :integer then number_field(method, options)
  when :float then text_field(method, options)
  else
    case method.to_s
    when /password/ then password_field(method, options)
      # FIXME: Possibly use time_zone_select with dropdown?
    when /time_zone/ then time_zone_select(method, options.delete(:priority_zones), options,
                                           { class: 'custom-select form-control' })
    # FIXME: Possibly use country_select with dropdown?
    when /country/ then country_select(method, options, class: 'custom-select form-control')
    when /email/ then email_field(method, options)
    when /phone/ then phone_input(method, options)
    when /url/ then url_field(method, options)
    else
      text_field(method, options)
    end
  end
end

#string_input(method, options = {}) ⇒ Object

Inputs and helpers



241
242
243
244
245
246
247
248
249
250
251
# File 'lib/satis/forms/builder.rb', line 241

def string_input(method, options = {})
  form_group(method, options) do
    safe_join [
      (custom_label(method, options[:label], options) unless options[:label] == false),
      string_field(method,
                   merge_input_options({ as: options[:as], class: "form-control #{if has_error?(method)
                                                                                    'is-invalid'
                                                                                  end}" }, options[:input_html]))
    ]
  end
end

#switch(method, options = {}, &block) ⇒ Object

A switch backed by a hidden value



126
127
128
129
130
# File 'lib/satis/forms/builder.rb', line 126

def switch(method, options = {}, &block)
  @form_options = options

  switch_input(method, options, &block)
end

#switch_input(method, options = {}, &block) ⇒ Object

Switch Pass icon: false for no icon



315
316
317
318
319
320
# File 'lib/satis/forms/builder.rb', line 315

def switch_input(method, options = {}, &block)
  form_group(method, options) do
    render(Satis::Switch::Component.new(form: self, attribute: method, title: options[:label], **options,
&block))
  end
end

#text_input(method, options = {}) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
# File 'lib/satis/forms/builder.rb', line 265

def text_input(method, options = {})
  form_group(method, options) do
    safe_join [
      (custom_label(method, options[:label], options) unless options[:label] == false),
      text_area(method,
                merge_input_options({ class: "form-control #{if has_error?(method)
                                                               'is-invalid'
                                                             end}" }, options[:input_html]))
    ]
  end
end