Class: Satis::Forms::Builder

Inherits:
ActionView::Helpers::FormBuilder
  • Object
show all
Includes:
Concerns::ContextualTranslations, 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.



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

def assocation
  @assocation
end

#templateObject (readonly)

Returns the value of attribute template.



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

def template
  @template
end

Instance Method Details

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

Simple-form like association



54
55
56
57
58
59
60
61
62
63
# File 'lib/satis/forms/builder.rb', line 54

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



350
351
352
353
354
355
356
357
358
359
# File 'lib/satis/forms/builder.rb', line 350

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



446
447
448
# File 'lib/satis/forms/builder.rb', line 446

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

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



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/satis/forms/builder.rb', line 416

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 [
      (custom_label(method, options[:label], options) unless options[:label] == false),
      (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

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



146
147
148
149
150
# File 'lib/satis/forms/builder.rb', line 146

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

  color_input(method, options, &block)
end

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

Color



371
372
373
374
375
376
# File 'lib/satis/forms/builder.rb', line 371

def color_input(method, options = {}, &block)
  form_group(method, options) do
    render(Satis::ColorPicker::Component.new(form: self, attribute: method, **options,
      &block))
  end
end

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



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

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 } }



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/satis/forms/builder.rb', line 379

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



47
48
49
50
51
# File 'lib/satis/forms/builder.rb', line 47

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

  editor_input(method, options, &block)
end

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



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/satis/forms/builder.rb', line 316

def editor_input(method, options = {}, &block)
  form_group(method, options) do
    safe_join [
      (custom_label(method, options[:label], options) unless options[:label] == false),
      @template.render(Satis::Editor::Component.new(form: self, attribute: method, **options, &block))
    ]
  end
  # 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 hidden',
  #                                                       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}", data: {
  #                 controller: 'satis-editor'
  #               }),
  #               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



200
201
202
203
204
205
206
207
208
# File 'lib/satis/forms/builder.rb', line 200

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


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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/satis/forms/builder.rb', line 76

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



483
484
485
486
487
488
489
490
491
492
493
# File 'lib/satis/forms/builder.rb', line 483

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}"] = h_v
      end
    else
      h[k] = v
    end
  end
end

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



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

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

#has_error?(method) ⇒ Boolean

Returns:

  • (Boolean)


219
220
221
222
223
# File 'lib/satis/forms/builder.rb', line 219

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

  @object.errors.key?(method)
end

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



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

def help(method, options = {})
  text = options[:help].presence || Satis.config.default_help_text.call(@template, @object, method,
    @options[:help_scope] || options[:help_scope])

  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



153
154
155
156
157
# File 'lib/satis/forms/builder.rb', line 153

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

  hidden_input(method, options, &block)
end

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



252
253
254
# File 'lib/satis/forms/builder.rb', line 252

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

#hint_text(text) ⇒ Object



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

def hint_text(text)
  return if text.nil?

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

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

Regular input



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

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_array(method, options = {}) ⇒ Object



307
308
309
310
311
312
313
314
# File 'lib/satis/forms/builder.rb', line 307

def input_array(method, options = {})
  form_group(method, options) do
    safe_join [
      (custom_label(method, options[:label], options) unless options[:label] == false),
      render(Satis::InputArray::Component.new(form: self, attribute: method, **options))
    ]
  end
end

#input_type_for(method, options) ⇒ Object

Non public



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/satis/forms/builder.rb', line 161

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



476
477
478
479
480
481
# File 'lib/satis/forms/builder.rb', line 476

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



281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/satis/forms/builder.rb', line 281

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



210
211
212
213
214
215
216
217
# File 'lib/satis/forms/builder.rb', line 210

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

#original_view_contextObject



25
26
27
# File 'lib/satis/forms/builder.rb', line 25

def original_view_context
  @template
end

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



256
257
258
# File 'lib/satis/forms/builder.rb', line 256

def password_input(method, options = {})
  string_input(method, options.merge(as: :password))
end

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



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

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



442
443
444
# File 'lib/satis/forms/builder.rb', line 442

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

#rails_fields_forObject



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

alias_method :rails_fields_for, :fields_for

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



237
238
239
240
241
# File 'lib/satis/forms/builder.rb', line 237

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

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

#rich_text(*args) ⇒ Object



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

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



450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/satis/forms/builder.rb', line 450

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



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/satis/forms/builder.rb', line 261

def string_input(method, options = {})
  orig_data = options.fetch(:data, {}).merge(controller: "satis-input")
  scrollable = options.fetch(:scrollable, false)

  css_class = ["form-control"]
  css_class << "is-invalid" if has_error?(method)
  css_class << "noscroll" unless scrollable

  data = options[:input_html].fetch(:data, {})
  data = data.merge("satis-input-target" => "input")
  options[:input_html] = options[:input_html].merge(data: data)

  form_group(method, options.merge(data: orig_data)) do
    safe_join [
      (custom_label(method, options[:label], options) unless options[:label] == false),
      string_field(method, merge_input_options({as: options[:as], class: "#{css_class.join(" ")}"}, options[:input_html]))
    ]
  end
end

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

A switch backed by a hidden value



140
141
142
143
144
# File 'lib/satis/forms/builder.rb', line 140

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



363
364
365
366
367
368
# File 'lib/satis/forms/builder.rb', line 363

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



294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/satis/forms/builder.rb', line 294

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