class Pakyow::Presenter::Presenters::Form

Constants

ACTION_METHODS
ID_LABEL

@api private

SUPPORTED_ACTIONS

Public Class Methods

connect_input_to_label(input, label) click to toggle source

@api private

# File lib/pakyow/presenter/presenters/form.rb, line 25
def connect_input_to_label(input, label)
  if false || input.attributes[:id].to_s.empty?
    id = SecureRandom.hex(4)
    input.attributes[:id] = id
  else
    id = input.attributes[:id]
  end

  label.attributes[:for] = id
end
pluralize_field_name(field) click to toggle source

@api private

# File lib/pakyow/presenter/presenters/form.rb, line 18
def pluralize_field_name(field)
  unless field.attributes[:name].to_s.end_with?("[]") || field.attributes[:name].to_s.empty?
    field.attributes[:name] = "#{field.attributes[:name]}[]"
  end
end

Public Instance Methods

action=(action) click to toggle source

Sets the form action (where it submits to).

# File lib/pakyow/presenter/presenters/form.rb, line 59
def action=(action)
  if action.is_a?(Symbol)
    if endpoint = app.endpoints.find(name: action)
      view.object.set_label(:endpoint, action)
      view.object.set_label(:endpoint_object, endpoint)
      view.object.set_label(:endpoint_params, {})
      Endpoint.new(__getobj__).setup
    end
  else
    attrs[:action] = action
  end
end
create(object = {}) { |self| ... } click to toggle source

Setup the form for creating an object.

# File lib/pakyow/presenter/presenters/form.rb, line 154
def create(object = {})
  yield self if block_given?
  setup_form_for_binding(:create, object)
end
delete(object) { |self| ... } click to toggle source

Setup the form for removing an object.

# File lib/pakyow/presenter/presenters/form.rb, line 175
def delete(object)
  yield self if block_given?
  setup_form_for_binding(:delete, object)
end
grouped_options_for(field, options = nil) { || ... } click to toggle source

Populates a select field with grouped options.

# File lib/pakyow/presenter/presenters/form.rb, line 115
def grouped_options_for(field, options = nil)
  unless field_presenter = find(field)
    raise ArgumentError.new("could not find field named `#{field}'")
  end

  unless grouped_options_for_allowed?(field_presenter)
    raise ArgumentError.new("expected `#{field}' to be a select field")
  end

  options = options || yield
  case field_presenter.object.tagname
  when "select"
    create_grouped_select_options(options, field_presenter)
  end
end
id() click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 53
def id
  label(ID_LABEL)
end
method=(method) click to toggle source

Sets the form method. Automatically handles method overrides by prepending a hidden field named `pw-http-method` when method is not get or post, setting the form method to post.

# File lib/pakyow/presenter/presenters/form.rb, line 75
def method=(method)
  method = method.to_s.downcase
  if method_override_required?(method)
    attrs[:method] = "post"

    find_or_create_method_override_input.attributes[:value] = method
  else
    attrs[:method] = method
  end
end
method_missing(method_name, *args, &block) click to toggle source

Delegate private methods.

# File lib/pakyow/presenter/presenters/form.rb, line 188
def method_missing(method_name, *args, &block)
  __getobj__.send(method_name, *args, &block)
end
object_for_form() click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 47
def object_for_form
  if labeled?(:binding)
    presentables[channeled_binding_name]
  end
end
options_for(field, options = nil) { |field_presenter| ... } click to toggle source

Populates a select field with options.

# File lib/pakyow/presenter/presenters/form.rb, line 88
def options_for(field, options = nil)
  unless field_presenter = find(Support.inflector.singularize(field)) || find(Support.inflector.pluralize(field))
    raise ArgumentError.new("could not find field named `#{field}'")
  end

  unless options_for_allowed?(field_presenter)
    raise ArgumentError.new("expected `#{field}' to be a select field, checkbox, radio button, or binding")
  end

  options = if block_given?
    yield(field_presenter)
  else
    options
  end

  case field_presenter.object.tagname
  when "select"
    create_select_options(options, field_presenter)
  when "input"
    create_input_options(options, field_presenter)
  else
    create_options(options, field_presenter)
  end
end
pp(*args) click to toggle source

Fixes an issue using pp inside a delegator.

# File lib/pakyow/presenter/presenters/form.rb, line 182
def pp(*args)
  Kernel.pp(*args)
end
replace(object) { |self| ... } click to toggle source

Setup the form for replacing an object.

# File lib/pakyow/presenter/presenters/form.rb, line 168
def replace(object)
  yield self if block_given?
  setup_form_for_binding(:replace, object)
end
respond_to_missing?(method_name, include_private = false) click to toggle source
Calls superclass method
# File lib/pakyow/presenter/presenters/form.rb, line 192
def respond_to_missing?(method_name, include_private = false)
  super || __getobj__.respond_to?(method_name, true)
end
setup(object = {}) { |self| ... } click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 131
def setup(object = {})
  use_binding_nodes
  use_global_options

  if block_given?
    yield self
  end

  bind(object)
  setup!
  self
end
setup?() click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 144
def setup?
  view.object.labeled?(:__form_setup)
end
update(object) { |self| ... } click to toggle source

Setup the form for updating an object.

# File lib/pakyow/presenter/presenters/form.rb, line 161
def update(object)
  yield self if block_given?
  setup_form_for_binding(:update, object)
end

Private Instance Methods

add_options_to_select(options, field_presenter) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 513
def add_options_to_select(options, field_presenter)
  # remove existing options
  field_presenter.clear

  # add generated options
  field_presenter.append(html_safe(options.to_xml))
end
create_grouped_select_options(values, field_presenter) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 295
def create_grouped_select_options(values, field_presenter)
  options = Oga::XML::Document.new

  values.each do |group_name, grouped_values|
    group = Oga::XML::Element.new(name: "optgroup")
    group.set("label", ensure_html_safety(group_name.to_s))
    options.children << group

    grouped_values.each do |value|
      group.children << create_select_option(value, field_presenter)
    end
  end

  add_options_to_select(options, field_presenter)
end
create_input_options(values, field_presenter) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 333
def create_input_options(values, field_presenter)
  if values.is_a?(Array) && field_presenter.attributes[:type] != "radio"
    self.class.pluralize_field_name(field_presenter.object)
  end

  values = Array.ensure(values).compact

  if values.any?
    field_view = field_presenter.view
    field_template = field_view.soft_copy
    insertable_field = field_view
    current_field = field_view

    values.each do |value|
      current_field.attributes[:value] = option_value(value, field_presenter.view).to_s

      unless current_field.equal?(field_view)
        insertable_field.after(current_field)
        insertable_field = current_field
      end

      current_field = field_template.soft_copy
    end
  else
    field_presenter.remove
  end
end
create_options(original_values, field_presenter) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 361
def create_options(original_values, field_presenter)
  values = Array.ensure(original_values).compact

  if values.any?
    field_view = field_presenter.view
    template = field_view.soft_copy
    insertable = field_view
    current = field_view

    values.each do |value|
      if treat_as_nested?(current, value)
        # We bind to a view here to avoid checking the value when setting up the option.
        #
        View.from_object(current.object).bind(value)

        # Set the field names appropriately.
        #
        current.object.each_significant_node(:field) do |field|
          name = "#{view.object.label(:binding)}[#{current.label(:binding)}]"
          name = if original_values.is_a?(Array)
            "#{name}[][#{field.label(:binding)}]"
          else
            "#{name}[#{field.label(:binding)}]"
          end

          field.attributes[:name] = name
        end

        # Insert a hidden field to identify the data on submission.
        #
        if key = option_value_keys(current, value).find { |k| value.include?(k) }
          id_input = Oga::XML::Element.new(name: "input")
          id_input.set(:type, "hidden")

          name = "#{view.object.label(:binding)}[#{current.label(:binding)}]"
          name = if original_values.is_a?(Array)
            "#{name}[][#{key}]"
          else
            "#{name}[#{key}]"
          end

          id_input.set(:name, name)
          id_input.set(:value, presentables[:__verifier].sign(
            ensure_html_safety(value[key].to_s)
          ))

          current.prepend(html_safe(id_input.to_xml))
        end
      else
        if input = current.object.find_first_significant_node(:field)
          input.attributes[:name] = "#{view.object.label(:binding)}[#{current.label(:binding)}]"

          if original_values.is_a?(Array) && input.attributes[:type] != "radio"
            self.class.pluralize_field_name(input)
          end

          input.attributes[:value] = ensure_html_safety(option_value(value, current).to_s)
        end

        if label = current.object.find_first_significant_node(:label)
          label.html = ensure_html_safety(label_value(value, label).to_s)
          label.set_label(:bound, true)
        end

        if input && label
          self.class.connect_input_to_label(input, label)
        end
      end

      unless current.equal?(field_view)
        insertable.after(current)
        insertable = current
      end

      current.object.set_label(:bound, true)
      current = template.soft_copy
    end
  else
    field_presenter.remove
  end
end
create_select_option(value, view) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 311
def create_select_option(value, view)
  option_binding = if option = view.object.find_first_significant_node(:option)
    option.label(:binding)
  else
    nil
  end

  Oga::XML::Element.new(name: "option").tap do |option_node|
    option_node.set("value", ensure_html_safety(option_value(value, view).to_s))

    display_value = if value.is_a?(Array)
      value[1]
    elsif option_binding && value.respond_to?(:[])
      value[option_binding.to_sym]
    else
      nil
    end

    option_node.inner_text = ensure_html_safety(display_value.to_s)
  end
end
create_select_options(values, field_presenter) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 285
def create_select_options(values, field_presenter)
  options = Oga::XML::Document.new

  Array.ensure(values).compact.each do |value|
    options.children << create_select_option(value, field_presenter)
  end

  add_options_to_select(options, field_presenter)
end
find_or_create_method_override_input() click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 276
def find_or_create_method_override_input
  unless input = view.object.find_first_significant_node(:method_override)
    prepend(method_override_input)
    input = view.object.find_first_significant_node(:method_override)
  end

  input
end
form_action_for_binding(action, object) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 244
def form_action_for_binding(action, object)
  [
    Support.inflector.singularize(label(:binding)).to_sym,
    Support.inflector.pluralize(label(:binding)).to_sym
  ].map { |possible_endpoint_name|
    params = Hash[label(:binding_info).to_h.map { |key, value|
      [:"#{key}_id", value]
    }].merge(object.to_h)

    endpoint_path = label(:binding_info).to_h.keys.map { |key|
      Support.inflector.pluralize(key).to_sym
    }

    endpoint_path << possible_endpoint_name
    endpoint_path.uniq!

    app.endpoints.path_to(*endpoint_path, action, **params)
  }.compact.first
end
grouped_options_for_allowed?(field_presenter) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 509
def grouped_options_for_allowed?(field_presenter)
  field_presenter.object.tagname == "select"
end
label_value(value, view) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 474
def label_value(value, view)
  if value.is_a?(Array)
    value[1]
  elsif value.is_a?(String)
    value
  elsif view.labeled?(:binding) && value.respond_to?(:[])
    value[view.label(:binding)]
  else
    nil
  end
end
method_for_action(action) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 264
def method_for_action(action)
  ACTION_METHODS[action]
end
method_override_input() click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 272
def method_override_input
  html_safe("<input type=\"hidden\" name=\"pw-http-method\">")
end
method_override_required?(method) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 268
def method_override_required?(method)
  method != "get" && method != "post"
end
option_value(value, view) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 456
def option_value(value, view)
  if value.is_a?(Array)
    value[0]
  elsif value.is_a?(String)
    value
  elsif value.respond_to?(:[])
    option_value_keys(view, value).each do |key|
      if value.include?(key)
        return value[key]
      end
    end

    nil
  else
    value.to_s
  end
end
option_value_keys(view, value, include_binding_prop = true) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 486
def option_value_keys(view, value, include_binding_prop = true)
  [].tap do |keys|
    if include_binding_prop
      keys << view.object.label(:binding_prop)
    end

    if value.class.respond_to?(:primary_key_field)
      keys << value.class.primary_key_field
    end

    keys << :id
  end.compact
end
options_for_allowed?(field_presenter) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 500
def options_for_allowed?(field_presenter)
  field_presenter.object.tagname == "select" || (
    field_presenter.object.tagname == "input" && (
      field_presenter.object.attributes[:type] == "checkbox" || field_presenter.object.attributes[:type] == "radio"
    ) ||
    field_presenter.object.significant?(:binding)
  )
end
setup!() click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 148
        def setup!
  view.object.set_label(:__form_setup, true)
end
setup_form_for_binding(action, object) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 198
def setup_form_for_binding(action, object)
  setup(object) do
    if SUPPORTED_ACTIONS.include?(action)
      unless labeled?(:endpoint)
        if self.action = form_action_for_binding(action, object)
          self.method = method_for_action(action)
        end
      end
    else
      raise ArgumentError.new("expected action to be one of: #{SUPPORTED_ACTIONS.join(", ")}")
    end
  end
end
treat_as_nested?(view, value) click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 443
def treat_as_nested?(view, value)
  if value.is_a?(Array)
    false
  else
    keys = option_value_keys(view, value, false)
    view.object.each_significant_node(:field) do |field|
      return true if field.labeled?(:binding) && !keys.include?(field.label(:binding))
    end

    false
  end
end
use_binding_nodes() click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 212
def use_binding_nodes
  view.object.set_label(:bound, true)

  view.object.each_significant_node(:binding, descend: true) do |object|
    if Pakyow::Presenter::Views::Form::FIELD_TAGS.include?(object.tagname)
      object.set_label(:bound, true)
    end
  end
end
use_global_options() click to toggle source
# File lib/pakyow/presenter/presenters/form.rb, line 222
def use_global_options
  __getobj__.class.__global_options.each do |form_binding, options|
    form = if view.object.tagname == "form" && view.binding_name == form_binding
      self
    else
      form(form_binding)
    end

    if form
      options.each do |field_binding, metadata|
        if metadata[:block]
          form.options_for(field_binding) do |context|
            instance_exec(context, &metadata[:block])
          end
        else
          form.options_for(field_binding, metadata[:options])
        end
      end
    end
  end
end