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



344
345
346
347
348
349
350
351
352
353
# File 'lib/satis/forms/builder.rb', line 344

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



432
433
434
# File 'lib/satis/forms/builder.rb', line 432

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

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



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/satis/forms/builder.rb', line 402

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

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



219
220
221
222
223
224
225
226
227
228
229
# File 'lib/satis/forms/builder.rb', line 219

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



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/satis/forms/builder.rb', line 365

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



310
311
312
313
314
315
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
# File 'lib/satis/forms/builder.rb', line 310

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



194
195
196
197
198
199
200
201
202
# File 'lib/satis/forms/builder.rb', line 194

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



469
470
471
472
473
474
475
476
477
478
479
# File 'lib/satis/forms/builder.rb', line 469

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



175
176
177
178
179
180
181
182
183
# File 'lib/satis/forms/builder.rb', line 175

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)


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

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

  @object.errors.key?(method)
end

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



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

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



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

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

  hidden_input(method, options, &block)
end

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



246
247
248
# File 'lib/satis/forms/builder.rb', line 246

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

#hint_text(text) ⇒ Object



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

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



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

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



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/satis/forms/builder.rb', line 155

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



462
463
464
465
466
467
# File 'lib/satis/forms/builder.rb', line 462

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



275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/satis/forms/builder.rb', line 275

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



204
205
206
207
208
209
210
211
# File 'lib/satis/forms/builder.rb', line 204

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



250
251
252
# File 'lib/satis/forms/builder.rb', line 250

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

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



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/satis/forms/builder.rb', line 381

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



428
429
430
# File 'lib/satis/forms/builder.rb', line 428

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



231
232
233
234
235
# File 'lib/satis/forms/builder.rb', line 231

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



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/satis/forms/builder.rb', line 436

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



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/satis/forms/builder.rb', line 255

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



357
358
359
360
361
362
# File 'lib/satis/forms/builder.rb', line 357

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



288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/satis/forms/builder.rb', line 288

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