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

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.



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
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
# File 'app/lib/primer/forms/dsl/input.rb', line 57

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[: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)

  # 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

  # Only set `for` when the id is known. Otherwise leave it unset so
  # Rails generates the scoped id for both label and input; passing
  # `for: nil` would suppress that and drop the association.
  @label_arguments[:for] = @input_arguments[:id] if @input_arguments[:id].present?

  # 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[:character_limit_caption] = "character_limit-#{@base_id}" if character_limit?
    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.



53
54
55
# File 'app/lib/primer/forms/dsl/input.rb', line 53

def base_id
  @base_id
end

#builderObject (readonly)

Returns the value of attribute builder.



53
54
55
# File 'app/lib/primer/forms/dsl/input.rb', line 53

def builder
  @builder
end

#captionObject (readonly)

Returns the value of attribute caption.



53
54
55
# File 'app/lib/primer/forms/dsl/input.rb', line 53

def caption
  @caption
end

#formObject (readonly)

Returns the value of attribute form.



53
54
55
# File 'app/lib/primer/forms/dsl/input.rb', line 53

def form
  @form
end

#form_controlObject (readonly) Also known as: form_control?

Returns the value of attribute form_control.



53
54
55
# File 'app/lib/primer/forms/dsl/input.rb', line 53

def form_control
  @form_control
end

#idsObject (readonly)

Returns the value of attribute ids.



53
54
55
# File 'app/lib/primer/forms/dsl/input.rb', line 53

def ids
  @ids
end

#input_argumentsObject (readonly)

Returns the value of attribute input_arguments.



53
54
55
# File 'app/lib/primer/forms/dsl/input.rb', line 53

def input_arguments
  @input_arguments
end

#label_argumentsObject (readonly)

Returns the value of attribute label_arguments.



53
54
55
# File 'app/lib/primer/forms/dsl/input.rb', line 53

def label_arguments
  @label_arguments
end

#validation_messageObject (readonly)

Returns the value of attribute validation_message.



53
54
55
# File 'app/lib/primer/forms/dsl/input.rb', line 53

def validation_message
  @validation_message
end

Instance Method Details

#add_input_aria(key, value) ⇒ Object



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

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



134
135
136
137
138
# File 'app/lib/primer/forms/dsl/input.rb', line 134

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

#add_input_data(key, value) ⇒ Object



156
157
158
# File 'app/lib/primer/forms/dsl/input.rb', line 156

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

#add_label_classes(*class_names) ⇒ Object



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

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

#autofocus!Object



268
269
270
# File 'app/lib/primer/forms/dsl/input.rb', line 268

def autofocus!
  input_arguments[:autofocus] = true
end

#caption?Boolean

Returns:

  • (Boolean)


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

def caption?
  caption.present?
end

#caption_idObject



189
190
191
# File 'app/lib/primer/forms/dsl/input.rb', line 189

def caption_id
  ids[:caption]
end

#caption_template?Boolean

Returns:

  • (Boolean)


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

def caption_template?
  return false unless form

  form.caption_template?(caption_template_name)
end

#character_limit?Boolean

Returns:

  • (Boolean)


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

def character_limit?
  false
end

#character_limit_idObject



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

def character_limit_id
  ids[:character_limit_caption]
end

#character_limit_target_prefixObject



215
216
217
218
219
220
221
222
223
224
# File 'app/lib/primer/forms/dsl/input.rb', line 215

def character_limit_target_prefix
  case type
  when :text_field
    "primer-text-field"
  when :text_area
    "primer-text-area"
  else
    ""
  end
end

#disabled?Boolean

Returns:

  • (Boolean)


245
246
247
# File 'app/lib/primer/forms/dsl/input.rb', line 245

def disabled?
  !!input_arguments[:disabled]
end

#focusable?Boolean

:nocov:

Returns:

  • (Boolean)


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

def focusable?
  false
end

#full_width?Boolean

Returns:

  • (Boolean)


249
250
251
# File 'app/lib/primer/forms/dsl/input.rb', line 249

def full_width?
  @full_width
end

#hidden?Boolean

Returns:

  • (Boolean)


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

def hidden?
  !!input_arguments[:hidden]
end

#idObject



272
273
274
# File 'app/lib/primer/forms/dsl/input.rb', line 272

def id
  @input_arguments[:id]
end

#input?Boolean

Returns:

  • (Boolean)


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

def input?
  true
end

#invalid?Boolean

Returns:

  • (Boolean)


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

def invalid?
  supports_validation? && !valid?
end

#labelObject



285
286
287
# File 'app/lib/primer/forms/dsl/input.rb', line 285

def label
  raise_for_abstract_method!(__method__)
end

#merge_input_arguments!(arguments) ⇒ Object

:nocov:



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'app/lib/primer/forms/dsl/input.rb', line 166

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:



281
282
283
# File 'app/lib/primer/forms/dsl/input.rb', line 281

def name
  raise_for_abstract_method!(__method__)
end

#remove_input_data(key) ⇒ Object

:nocov:



161
162
163
# File 'app/lib/primer/forms/dsl/input.rb', line 161

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

#render_caption_templateObject



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

def render_caption_template
  form.render_caption_template(caption_template_name)
end

#required?Boolean

Returns:

  • (Boolean)


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

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

#sizeObject



253
254
255
# File 'app/lib/primer/forms/dsl/input.rb', line 253

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

#supports_validation?Boolean

Returns:

  • (Boolean)


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

def supports_validation?
  true
end

#to_componentObject



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

def to_component
  raise_for_abstract_method!(__method__)
end

#typeObject



289
290
291
# File 'app/lib/primer/forms/dsl/input.rb', line 289

def type
  raise_for_abstract_method!(__method__)
end

#valid?Boolean

Returns:

  • (Boolean)


226
227
228
# File 'app/lib/primer/forms/dsl/input.rb', line 226

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

#validation_argumentsObject



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

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

#validation_error_icon_targetObject



326
327
328
# File 'app/lib/primer/forms/dsl/input.rb', line 326

def validation_error_icon_target
  ""
end

#validation_idObject



185
186
187
# File 'app/lib/primer/forms/dsl/input.rb', line 185

def validation_id
  ids[:validation]
end

#validation_message_argumentsObject



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

def validation_message_arguments
  {}
end

#validation_messagesObject



257
258
259
260
261
262
263
264
265
266
# File 'app/lib/primer/forms/dsl/input.rb', line 257

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



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

def validation_success_icon_target
  ""
end

#valueObject



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

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)


334
335
336
# File 'app/lib/primer/forms/dsl/input.rb', line 334

def values_disambiguate_template_names?
  false
end