Class: Primer::Forms::Dsl::Input

Inherits:
Object
  • Object
show all
Includes:
ClassNameHelper
Defined in:
app/lib/primer/forms/dsl/input.rb

Overview

:nodoc:

Constant Summary collapse

SPACE_DELIMITED_ARIA_ATTRIBUTES =
%i[describedby].freeze
DEFAULT_SIZE =
:medium
SIZE_MAPPINGS =
{
  :small => "FormControl-small",
  DEFAULT_SIZE => "FormControl-medium",
  :large => "FormControl-large"
}.freeze
SIZE_OPTIONS =
SIZE_MAPPINGS.keys
DEFAULT_INPUT_WIDTH =
:auto
INPUT_WIDTH_MAPPINGS =
{
  DEFAULT_INPUT_WIDTH => "FormControl-input-width--auto",
  :xsmall => "FormControl-input-width--xsmall",
  :small => "FormControl-input-width--small",
  :medium => "FormControl-input-width--medium",
  :large => "FormControl-input-width--large",
  :xlarge => "FormControl-input-width--xlarge",
  :xxlarge => "FormControl-input-width--xxlarge"
}.freeze
INPUT_WIDTH_OPTIONS =
INPUT_WIDTH_MAPPINGS.keys

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ClassNameHelper

#class_names

Constructor Details

#initialize(builder:, form:, **system_arguments) ⇒ Input

Returns a new instance of Input.



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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'app/lib/primer/forms/dsl/input.rb', line 69

def initialize(builder:, form:, **system_arguments)
  @builder = builder
  @form = form

  @input_arguments = system_arguments
  @input_arguments.delete(:id) unless @input_arguments[:id].present?
  @label_arguments = @input_arguments.delete(:label_arguments) || {}
  @label_arguments[:for] = id if id.present?

  @label_arguments[:class] = class_names(
    @label_arguments[:class],
    @input_arguments.fetch(:visually_hide_label, false) ? "sr-only" : nil
  )

  @input_arguments.delete(:visually_hide_label)

  @input_arguments.delete(:class) if @input_arguments[:class].blank?
  @label_arguments.delete(:class) if @label_arguments[:class].blank?

  @caption = @input_arguments.delete(:caption)
  @validation_message = @input_arguments.delete(:validation_message)
  @invalid = @input_arguments.delete(:invalid)
  @full_width = @input_arguments.delete(:full_width) { true }
  @size = @input_arguments.delete(:size)
  @input_width = @input_arguments.delete(:input_width)

  # If scope_name_to_model is false, the name of the input for eg. `my_field`
  # will be `my_field` instead of the Rails default of `model[my_field]`.
  #
  # We achieve this by passing the `name` option to Rails form builder
  # methods. These methods will use the passed name if provided instead
  # of generating a scoped one.
  #
  unless @input_arguments.delete(:scope_name_to_model) { true }
    @input_arguments[:name] = name
  end
  # rubocop:enable Style/IfUnlessModifier

  # If scope_id_to_model is false, the name of the input for eg. `my_field`
  # will be `my_field` instead of the Rails default of `model_my_field`.
  #
  # We achieve this by passing the `id` option to Rails form builder
  # methods. These methods will use the passed id if provided instead
  # of generating a scoped one. The id is the name of the field unless
  # explicitly provided in system_arguments.
  #
  # rubocop:disable Style/IfUnlessModifier
  unless @input_arguments.delete(:scope_id_to_model) { true }
    @input_arguments[:id] = @input_arguments.delete(:id) { name }
  end
  # rubocop:enable Style/IfUnlessModifier

  # Whether or not to wrap the component in a FormControl, which renders a
  # label above and validation message beneath the input.
  @form_control = @input_arguments.delete(:form_control) { true }

  @input_arguments[:invalid] = "true" if invalid?

  @base_id = SecureRandom.uuid

  @ids = {}.tap do |id_map|
    id_map[:validation] = "validation-#{@base_id}" if supports_validation?
    id_map[:caption] = "caption-#{@base_id}" if caption? || caption_template?
  end

  add_input_aria(:required, true) if required?
  add_input_aria(:invalid, true) if invalid?
  add_input_aria(:describedby, ids.values) if ids.any?

  # avoid browser-native validation, which doesn't match Primer's style
  input_arguments.delete(:required)
end

Instance Attribute Details

#base_idObject (readonly)

Returns the value of attribute base_id.



65
66
67
# File 'app/lib/primer/forms/dsl/input.rb', line 65

def base_id
  @base_id
end

#builderObject (readonly)

Returns the value of attribute builder.



65
66
67
# File 'app/lib/primer/forms/dsl/input.rb', line 65

def builder
  @builder
end

#captionObject (readonly)

Returns the value of attribute caption.



65
66
67
# File 'app/lib/primer/forms/dsl/input.rb', line 65

def caption
  @caption
end

#formObject (readonly)

Returns the value of attribute form.



65
66
67
# File 'app/lib/primer/forms/dsl/input.rb', line 65

def form
  @form
end

#form_controlObject (readonly) Also known as: form_control?

Returns the value of attribute form_control.



65
66
67
# File 'app/lib/primer/forms/dsl/input.rb', line 65

def form_control
  @form_control
end

#idsObject (readonly)

Returns the value of attribute ids.



65
66
67
# File 'app/lib/primer/forms/dsl/input.rb', line 65

def ids
  @ids
end

#input_argumentsObject (readonly)

Returns the value of attribute input_arguments.



65
66
67
# File 'app/lib/primer/forms/dsl/input.rb', line 65

def input_arguments
  @input_arguments
end

#label_argumentsObject (readonly)

Returns the value of attribute label_arguments.



65
66
67
# File 'app/lib/primer/forms/dsl/input.rb', line 65

def label_arguments
  @label_arguments
end

#validation_messageObject (readonly)

Returns the value of attribute validation_message.



65
66
67
# File 'app/lib/primer/forms/dsl/input.rb', line 65

def validation_message
  @validation_message
end

Instance Method Details

#add_input_aria(key, value) ⇒ Object



154
155
156
157
158
159
160
161
162
# File 'app/lib/primer/forms/dsl/input.rb', line 154

def add_input_aria(key, value)
  @input_arguments[:aria] ||= {}

  @input_arguments[:aria][key] = if space_delimited_aria_attribute?(key)
                                   aria_join(@input_arguments[:aria][key], *Array(value))
                                 else
                                   value
                                 end
end

#add_input_classes(*class_names) ⇒ Object



142
143
144
145
146
# File 'app/lib/primer/forms/dsl/input.rb', line 142

def add_input_classes(*class_names)
  input_arguments[:class] = class_names(
    input_arguments[:class], *class_names
  )
end

#add_input_data(key, value) ⇒ Object



164
165
166
# File 'app/lib/primer/forms/dsl/input.rb', line 164

def add_input_data(key, value)
  input_data[key] = value
end

#add_label_classes(*class_names) ⇒ Object



148
149
150
151
152
# File 'app/lib/primer/forms/dsl/input.rb', line 148

def add_label_classes(*class_names)
  label_arguments[:class] = class_names(
    label_arguments[:class], *class_names
  )
end

#autofocus!Object



262
263
264
# File 'app/lib/primer/forms/dsl/input.rb', line 262

def autofocus!
  input_arguments[:autofocus] = true
end

#caption?Boolean

Returns:

  • (Boolean)


202
203
204
# File 'app/lib/primer/forms/dsl/input.rb', line 202

def caption?
  caption.present?
end

#caption_idObject



198
199
200
# File 'app/lib/primer/forms/dsl/input.rb', line 198

def caption_id
  ids[:caption]
end

#caption_template?Boolean

Returns:

  • (Boolean)


206
207
208
209
210
# File 'app/lib/primer/forms/dsl/input.rb', line 206

def caption_template?
  return false unless form

  form.caption_template?(caption_template_name)
end

#disabled?Boolean

Returns:

  • (Boolean)


235
236
237
# File 'app/lib/primer/forms/dsl/input.rb', line 235

def disabled?
  !!input_arguments[:disabled]
end

#focusable?Boolean

:nocov:

Returns:

  • (Boolean)


293
294
295
# File 'app/lib/primer/forms/dsl/input.rb', line 293

def focusable?
  false
end

#full_width?Boolean

Returns:

  • (Boolean)


239
240
241
# File 'app/lib/primer/forms/dsl/input.rb', line 239

def full_width?
  @full_width
end

#hidden?Boolean

Returns:

  • (Boolean)


224
225
226
# File 'app/lib/primer/forms/dsl/input.rb', line 224

def hidden?
  !!input_arguments[:hidden]
end

#idObject



266
267
268
# File 'app/lib/primer/forms/dsl/input.rb', line 266

def id
  @input_arguments[:id]
end

#input?Boolean

Returns:

  • (Boolean)


297
298
299
# File 'app/lib/primer/forms/dsl/input.rb', line 297

def input?
  true
end

#input_widthObject



247
248
249
# File 'app/lib/primer/forms/dsl/input.rb', line 247

def input_width
  @input_width ||= INPUT_WIDTH_MAPPINGS.include?(@input_width) ? @input_width : DEFAULT_INPUT_WIDTH
end

#invalid?Boolean

Returns:

  • (Boolean)


220
221
222
# File 'app/lib/primer/forms/dsl/input.rb', line 220

def invalid?
  supports_validation? && !valid?
end

#labelObject



279
280
281
# File 'app/lib/primer/forms/dsl/input.rb', line 279

def label
  raise_for_abstract_method!(__method__)
end

#merge_input_arguments!(arguments) ⇒ Object

:nocov:



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'app/lib/primer/forms/dsl/input.rb', line 175

def merge_input_arguments!(arguments)
  arguments.each do |k, v|
    case k
    when :class, :classes, "class", "classes"
      add_input_classes(v)
    when :aria, "aria"
      v.each do |aria_k, aria_v|
        add_input_aria(aria_k, aria_v)
      end
    when :data, "data"
      v.each do |data_k, data_v|
        add_input_data(data_k, data_v)
      end
    else
      @input_arguments[k] = v
    end
  end
end

#nameObject

:nocov:



275
276
277
# File 'app/lib/primer/forms/dsl/input.rb', line 275

def name
  raise_for_abstract_method!(__method__)
end

#remove_input_data(key) ⇒ Object

:nocov:



169
170
171
# File 'app/lib/primer/forms/dsl/input.rb', line 169

def remove_input_data(key)
  input_data.delete(key)
end

#render_caption_templateObject



212
213
214
# File 'app/lib/primer/forms/dsl/input.rb', line 212

def render_caption_template
  form.render_caption_template(caption_template_name)
end

#required?Boolean

Returns:

  • (Boolean)


228
229
230
231
232
233
# File 'app/lib/primer/forms/dsl/input.rb', line 228

def required?
  input_arguments[:required] ||
    input_arguments[:aria_required] ||
    input_arguments[:"aria-required"] ||
    input_arguments.dig(:aria, :required)
end

#sizeObject



243
244
245
# File 'app/lib/primer/forms/dsl/input.rb', line 243

def size
  @size ||= SIZE_MAPPINGS.include?(@size) ? @size : DEFAULT_SIZE
end

#supports_validation?Boolean

Returns:

  • (Boolean)


301
302
303
# File 'app/lib/primer/forms/dsl/input.rb', line 301

def supports_validation?
  true
end

#to_componentObject



287
288
289
# File 'app/lib/primer/forms/dsl/input.rb', line 287

def to_component
  raise_for_abstract_method!(__method__)
end

#typeObject



283
284
285
# File 'app/lib/primer/forms/dsl/input.rb', line 283

def type
  raise_for_abstract_method!(__method__)
end

#valid?Boolean

Returns:

  • (Boolean)


216
217
218
# File 'app/lib/primer/forms/dsl/input.rb', line 216

def valid?
  supports_validation? && validation_messages.empty? && !@invalid
end

#validation_argumentsObject



305
306
307
308
309
310
311
# File 'app/lib/primer/forms/dsl/input.rb', line 305

def validation_arguments
  {
    class: "FormControl-inlineValidation",
    id: validation_id,
    hidden: valid? || validation_messages.empty?
  }
end

#validation_error_icon_targetObject



321
322
323
# File 'app/lib/primer/forms/dsl/input.rb', line 321

def validation_error_icon_target
  ""
end

#validation_idObject



194
195
196
# File 'app/lib/primer/forms/dsl/input.rb', line 194

def validation_id
  ids[:validation]
end

#validation_message_argumentsObject



313
314
315
# File 'app/lib/primer/forms/dsl/input.rb', line 313

def validation_message_arguments
  {}
end

#validation_messagesObject



251
252
253
254
255
256
257
258
259
260
# File 'app/lib/primer/forms/dsl/input.rb', line 251

def validation_messages
  @validation_messages ||=
    if validation_message
      [validation_message]
    elsif builder.object.respond_to?(:errors)
      name ? builder.object.errors.full_messages_for(name) : []
    else
      []
    end
end

#validation_success_icon_targetObject



317
318
319
# File 'app/lib/primer/forms/dsl/input.rb', line 317

def validation_success_icon_target
  ""
end

#valueObject



270
271
272
# File 'app/lib/primer/forms/dsl/input.rb', line 270

def value
  @input_arguments[:value]
end

#values_disambiguate_template_names?Boolean

Whether or not the ‘value:` argument should be used to determine the caption template for a given field. This is useful in especially radio button groups where each option has the same name but a different value. Check box groups where the values are submitted as an array also use this feature, since each check box also has the same name.

Returns:

  • (Boolean)


329
330
331
# File 'app/lib/primer/forms/dsl/input.rb', line 329

def values_disambiguate_template_names?
  false
end