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

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



159
160
161
162
163
164
165
# File 'lib/satis/forms/builder.rb', line 159

def attachments(method, options = {}, &block)
  self.multipart = true
  safe_join [
    @template.render(Satis::Attachments::Component.new(object, method, form: self, **value_text_method_options(options),
      &block))
  ]
end

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



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

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



438
439
440
# File 'lib/satis/forms/builder.rb', line 438

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

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



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/satis/forms/builder.rb', line 408

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



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

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



233
234
235
236
237
238
239
240
241
242
243
# File 'lib/satis/forms/builder.rb', line 233

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



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/satis/forms/builder.rb', line 371

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



324
325
326
327
328
329
330
331
# File 'lib/satis/forms/builder.rb', line 324

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



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

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



475
476
477
478
479
480
481
482
483
484
485
# File 'lib/satis/forms/builder.rb', line 475

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



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

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

#has_error?(method) ⇒ Boolean

Returns:

  • (Boolean)


227
228
229
230
231
# File 'lib/satis/forms/builder.rb', line 227

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

  @object.errors.key?(method)
end

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



251
252
253
254
255
256
257
258
# File 'lib/satis/forms/builder.rb', line 251

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



260
261
262
# File 'lib/satis/forms/builder.rb', line 260

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

#hint_text(text) ⇒ Object



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

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



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

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



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/satis/forms/builder.rb', line 169

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



468
469
470
471
472
473
# File 'lib/satis/forms/builder.rb', line 468

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



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

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



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

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



264
265
266
# File 'lib/satis/forms/builder.rb', line 264

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

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



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/satis/forms/builder.rb', line 387

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



434
435
436
# File 'lib/satis/forms/builder.rb', line 434

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



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

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

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



333
334
335
336
337
338
339
340
# File 'lib/satis/forms/builder.rb', line 333

def signature(method, options = {}, &block)
  form_group(method, options) do
    safe_join [
      (custom_label(method, options[:label], options) unless options[:label] == false),
      @template.render(Satis::Signature::Component.new(form: self, attribute: method, **options, &block))
    ]
  end
end

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



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/satis/forms/builder.rb', line 442

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



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/satis/forms/builder.rb', line 269

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



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

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



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

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