Module: Layered::Ui::FormHelper

Defined in:
app/helpers/layered/ui/form_helper.rb

Constant Summary collapse

FIELD_TYPES =
%i[
  string text email password number tel url search
  date datetime time month week
  color range file
  select checkbox hidden combobox
].freeze

Instance Method Summary collapse

Instance Method Details

#l_ui_field_describedby(record, attribute, hint: false) ⇒ Object

Space-joined ids for a field's aria-describedby: its hint (when the field has one) followed by its error message, which is always rendered.



116
117
118
119
120
121
# File 'app/helpers/layered/ui/form_helper.rb', line 116

def l_ui_field_describedby(record, attribute, hint: false)
  ids = []
  ids << l_ui_field_hint_id(record, attribute) if hint
  ids << l_ui_field_error_id(record, attribute)
  ids.join(" ")
end

#l_ui_field_error_id(record, attribute) ⇒ Object



106
107
108
# File 'app/helpers/layered/ui/form_helper.rb', line 106

def l_ui_field_error_id(record, attribute)
  "#{record.model_name.param_key}_#{attribute}_error"
end

#l_ui_field_hint_id(record, attribute) ⇒ Object



110
111
112
# File 'app/helpers/layered/ui/form_helper.rb', line 110

def l_ui_field_hint_id(record, attribute)
  "#{record.model_name.param_key}_#{attribute}_hint"
end

#l_ui_field_value(record, attribute) ⇒ Object

The record's current value for a field, used as the default selection for a :combobox field when selected: is not given.



102
103
104
# File 'app/helpers/layered/ui/form_helper.rb', line 102

def l_ui_field_value(record, attribute)
  record.public_send(attribute) if record.respond_to?(attribute)
end

#l_ui_form(record, fields:, url:, method: nil, submit: nil, multipart: nil) ⇒ Object

Renders a complete form with all fields, error summary, and submit button.

l_ui_form(@post,
fields: Post.l_managed_resource_fields,
url: managed_posts_path)


18
19
20
21
22
# File 'app/helpers/layered/ui/form_helper.rb', line 18

def l_ui_form(record, fields:, url:, method: nil, submit: nil, multipart: nil)
  render partial: "layered/ui/managed_resource/form",
         locals: { record: record, fields: fields, url: url, method: method,
                   submit: submit, multipart: multipart }
end

#l_ui_normalise_field(record, config) ⇒ Object

Normalises a raw field config hash into a canonical form.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
# File 'app/helpers/layered/ui/form_helper.rb', line 25

def l_ui_normalise_field(record, config)
  attribute = config[:attribute]
  as = config[:as] || l_ui_field_type_for(record.class, attribute)

  unless FIELD_TYPES.include?(as)
    raise ArgumentError,
          "Unsupported field type :#{as} for :#{attribute}. " \
          "Supported types: #{FIELD_TYPES.map { |t| ":#{t}" }.join(', ')}"
  end

  if as == :select && config[:collection].nil?
    raise ArgumentError,
          "Field :#{attribute} is declared as :select but has no :collection. " \
          "Provide collection: [['Label', value], ...] or collection: -> { Model.pluck(:name, :id) }"
  end

  if as == :combobox && config[:collection].nil? && config[:url].nil?
    raise ArgumentError,
          "Field :#{attribute} is declared as :combobox but has no :collection or :url. " \
          "Provide collection: [['Label', value], ...] to filter in the browser, " \
          "or url: to fetch options from an endpoint as the user types"
  end

  if as == :combobox && config[:url] && !config.key?(:selected) &&
     l_ui_field_value(record, attribute).present?
    raise ArgumentError,
          "Field :#{attribute} is a remote :combobox with a value already set, but no :selected. " \
          "A remote collection cannot be searched for the label of an existing value, " \
          "so pass it with its label, e.g. selected: [[record.user.name, record.user_id]]"
  end

  label = config[:label] || attribute.to_s.humanize

  extras = config.except(:attribute, :as, :label, :required, :hint,
                         :collection, :placeholder, :prompt, :include_blank)

  if as == :combobox
    unusable = {
      prompt: "Use placeholder: instead, which a combobox shows until something is selected.",
      include_blank: "A combobox with no selection is already blank, so there is nothing to set."
    }
    unusable.each do |key, advice|
      next unless config.key?(key)

      raise ArgumentError,
            "Field :#{attribute} is a :combobox and cannot take :#{key}. #{advice}"
    end

    extras[:multiple] = l_ui_field_multiple?(record, attribute) unless config.key?(:multiple)

    allowed = ComboboxHelper::COMBOBOX_OPTIONS + [:class]
    unknown = extras.keys - allowed
    if unknown.any?
      raise ArgumentError,
            "Field :#{attribute} is a :combobox and cannot take " \
            "#{unknown.map { |key| ":#{key}" }.join(', ')}. Unlike the other field types, a " \
            "combobox takes only its own options (#{allowed.map { |key| ":#{key}" }.join(', ')}); " \
            "extra HTML attributes belong on container:"
    end
  end

  {
    attribute: attribute,
    as: as,
    label: label,
    required: config.fetch(:required, false),
    hint: config[:hint],
    collection: config[:collection],
    placeholder: config[:placeholder],
    prompt: config[:prompt],
    include_blank: config[:include_blank],
    extras: extras
  }
end