Class: Lifeform::Form

Inherits:
Object
  • Object
show all
Extended by:
Sequel::Inflections, Streamlined::Helpers
Includes:
Streamlined::Renderable
Defined in:
lib/lifeform/form.rb

Overview

A form object which stores field definitions and can be rendered as a component

Constant Summary collapse

MODEL_PATH_HELPER =
:polymorphic_path

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model = nil, url: nil, library: self.class.library, emit_form_tag: true, parent_name: nil, view_context: nil, **parameters) ⇒ Form

rubocop:disable Metrics/ParameterLists



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

def initialize( # rubocop:disable Metrics/ParameterLists
  model = nil,
  url: nil,
  library: self.class.library,
  emit_form_tag: true,
  parent_name: nil,
  view_context: nil,
  **parameters
)
  @model, @url, @library_name, @parameters, @emit_form_tag, @parent_name =
    model, url, library, parameters, emit_form_tag, parent_name
  @library = Libraries.const_get(self.class.send(:camelize, @library_name))
  @subform_instances = {}
  @_view_context = view_context

  self.class.initialize_field_definitions!

  @method = parameters[:method] ||= model.respond_to?(:persisted?) && model.persisted? ? :patch : :post
  parameters[:accept_charset] ||= "UTF-8"
  verify_method
end

Class Attribute Details

.rodauthObject



30
# File 'lib/lifeform/form.rb', line 30

def rodauth = Form.instance_variable_get(:@rodauth)

Instance Attribute Details

#emit_form_tagBoolean (readonly)

Returns:

  • (Boolean)


150
151
152
# File 'lib/lifeform/form.rb', line 150

def emit_form_tag
  @emit_form_tag
end

#libraryClass<Libraries::Default> (readonly)

Returns:



144
145
146
# File 'lib/lifeform/form.rb', line 144

def library
  @library
end

#modelObject (readonly)

Returns:

  • (Object)


138
139
140
# File 'lib/lifeform/form.rb', line 138

def model
  @model
end

#parametersHash (readonly)

Returns:

  • (Hash)


147
148
149
# File 'lib/lifeform/form.rb', line 147

def parameters
  @parameters
end

#parent_nameBoolean (readonly)

Returns:

  • (Boolean)


153
154
155
# File 'lib/lifeform/form.rb', line 153

def parent_name
  @parent_name
end

#urlString (readonly)

Returns:

  • (String)


141
142
143
# File 'lib/lifeform/form.rb', line 141

def url
  @url
end

Class Method Details

.configurationObject



32
# File 'lib/lifeform/form.rb', line 32

def configuration = @configuration ||= HashWithDotAccess::Hash.new

.disable_csrf_protection!Object



34
# File 'lib/lifeform/form.rb', line 34

def disable_csrf_protection! = configuration.disable_csrf_protection = true

.field(name, type: :text, library: self.library, **parameters) ⇒ Object



53
54
55
56
# File 'lib/lifeform/form.rb', line 53

def field(name, type: :text, library: self.library, **parameters)
  parameters[:name] = name.to_sym
  fields[name] = FieldDefinition.new(type, Libraries.const_get(camelize(library)), parameters)
end

.fields(&block) ⇒ Hash<Symbol, FieldDefinition>

Parameters:

  • block (Proc, nil)

Returns:



38
39
40
41
42
43
# File 'lib/lifeform/form.rb', line 38

def fields(&block)
  @fields ||= {}
  @fields_setup_block = block if block

  @fields
end

.fragment(name) {|form| ... } ⇒ Object

Add an HTML fragment which will be auto-rendered or manually rendered like any field. Use Streamlined helpers inside a squiggly heredoc passed via the block which will be executed within the view context (requires it have ‘include Streamlined::Helpers`)

Examples:

fragment :footer do |form| "<form-footer>\n  <p>\n    <img src=\"\#{text->{ asset_path(\"thanks.png\") }} />\n    Thanks for filling out our form!\n  </p>\n  \#{render form.field(:submit, label: t(\"users.sign_up\"))}\n</form-footer>\n"
end

Parameters:

  • name (Symbol)

Yield Parameters:



76
# File 'lib/lifeform/form.rb', line 76

def fragment(name, &block) = field(:"__#{name}", library: :default, type: :html, body: block)

.inherited(subclass) ⇒ Object



19
20
21
22
# File 'lib/lifeform/form.rb', line 19

def inherited(subclass)
  super
  subclass.library library
end

.initialize_field_definitions!Object



45
46
47
48
49
# File 'lib/lifeform/form.rb', line 45

def initialize_field_definitions!
  return unless @fields_setup_block

  @fields_setup_block.(configuration)
end

.library(library_name = nil) ⇒ Object



82
83
84
85
# File 'lib/lifeform/form.rb', line 82

def library(library_name = nil)
  @library = library_name.to_sym if library_name
  @library ||= :default
end

.name_of_model(model) ⇒ Object



119
120
121
122
123
124
125
126
127
128
# File 'lib/lifeform/form.rb', line 119

def name_of_model(model)
  return "" if model.nil?

  if model.respond_to?(:to_model)
    model.to_model.model_name.param_key
  else
    # Or just use basic underscore
    underscore(model.class.name).tr("/", "_")
  end
end

.param_keysArray<Symbol>

Returns:

  • (Array<Symbol>)


131
# File 'lib/lifeform/form.rb', line 131

def param_keys = fields.keys

.param_string_keysArray<String>

Returns:

  • (Array<String>)


134
# File 'lib/lifeform/form.rb', line 134

def param_string_keys = fields.keys.map(&:to_s)

.parameters_to_attributes(kwargs) ⇒ Object

Parameters:

  • kwargs (Hash)


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/lifeform/form.rb', line 103

def parameters_to_attributes(kwargs)
  attributes = {}
  kwargs.each do |key, value|
    case value
    when Hash
      value.each do |inner_key, inner_value|
        attributes[:"#{key}_#{inner_key}"] = process_value(inner_key, inner_value)
      end
    else
      attributes[key] = process_value(key, value) unless value.nil?
    end
  end

  attributes
end

.process_value(key, value) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/lifeform/form.rb', line 87

def process_value(key, value)
  return value if key == :if

  case value
  when TrueClass
    key.to_s
  when FalseClass
    nil
  when Symbol, Integer
    value.to_s
  else
    value
  end
end

.subform(name, klass, parent_name: nil) ⇒ Object



78
79
80
# File 'lib/lifeform/form.rb', line 78

def subform(name, klass, parent_name: nil)
  subforms[name.to_sym] = { class: klass, parent_name: parent_name }
end

.subformsObject



51
# File 'lib/lifeform/form.rb', line 51

def subforms = @subforms ||= {}

.tObject

Helper to point to ‘I18n.t` method



25
# File 'lib/lifeform/form.rb', line 25

def t(...) = I18n.t(...)

Instance Method Details

#add_authenticity_tokenObject



193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/lifeform/form.rb', line 193

def add_authenticity_token
  if helpers.respond_to?(:token_tag, true) # Rails
    helpers.send(:token_tag, nil, form_options: {
      action: parameters[:action].to_s,
      method: parameters[:method].to_s.downcase,
    })
  elsif helpers.respond_to?(:csrf_tag, true) # Roda
    helpers.send(:csrf_tag, parameters[:action].to_s, @method.to_s)
  else
    raise Lifeform::Error, "Missing token tag helper. Override `add_authenticity_token' in your Form object"
  end
end

#attributesObject



208
209
210
# File 'lib/lifeform/form.rb', line 208

def attributes
  @attributes ||= self.class.parameters_to_attributes(parameters)
end

#auto_render_fieldsObject



248
# File 'lib/lifeform/form.rb', line 248

def auto_render_fields = html_map(self.class.fields) { |k, _v| render(field(k)) }

#configurationObject



177
# File 'lib/lifeform/form.rb', line 177

def configuration = self.class.configuration

#field(name, **field_parameters) ⇒ Object



212
213
214
215
216
217
218
219
220
# File 'lib/lifeform/form.rb', line 212

def field(name, **field_parameters)
  # @type [FieldDefinition]
  field_definition = self.class.fields[name.to_sym]
  # @type [Class<Libraries::Default>]
  field_library = field_definition.library
  field_library.object_for_field_definition(
    self, field_definition, self.class.parameters_to_attributes(field_parameters)
  )
end

#fragment(name) ⇒ Object



222
# File 'lib/lifeform/form.rb', line 222

def fragment(name, **) = field(:"__#{name}", **)

#include_authenticity_token?Boolean

Returns:

  • (Boolean)


206
# File 'lib/lifeform/form.rb', line 206

def include_authenticity_token? = !configuration.disable_csrf_protection

#render(field_object) ⇒ Object



250
251
252
# File 'lib/lifeform/form.rb', line 250

def render(field_object)
  field_object.render_in(helpers)
end

#subform(name, model = nil) ⇒ Object



224
225
226
227
228
229
230
231
# File 'lib/lifeform/form.rb', line 224

def subform(name, model = nil)
  @subform_instances[name.to_sym] ||= self.class.subforms[name.to_sym][:class].new(
    model,
    emit_form_tag: false,
    parent_name: self.class.subforms[name.to_sym][:parent_name] || self.class.name_of_model(self.model),
    view_context: helpers
  )
end

#template(&block) ⇒ Object

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity



233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/lifeform/form.rb', line 233

def template(&block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
  form_tag = library::FORM_TAG
  parameters[:action] ||= url || (model ? helpers.send(self.class.const_get(:MODEL_PATH_HELPER), model) : nil)
  output_token = include_authenticity_token? && parameters[:method].to_s.casecmp("get").zero?.!

  html -> { "    <\#{form_tag} \#{html_attributes attributes}>\n      \#{add_authenticity_token if output_token}\n      \#{@method_tag&.() || \"\"}\n      \#{block ? capture(self, &block) : auto_render_fields}\n    </\#{form_tag}>\n  HTML\n  }\nend\n" # rubocop:disable Bridgetown/InsecureHeredoc

#verify_methodObject



179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/lifeform/form.rb', line 179

def verify_method
  return if %w[get post].include?(parameters[:method].to_s.downcase)

  method_value = @parameters[:method].to_s.downcase

  @method_tag = -> {
    "      <input type=\"hidden\" name=\"_method\" value=\"\#{text -> { method_value }}\" autocomplete=\"off\">\n    HTML\n  }\n\n  parameters[:method] = :post\nend\n"