decko-commons/decko

View on GitHub
mod/edit/set/all/form.rb

Summary

Maintainability
A
25 mins
Test Coverage
format :html do
  # FIELDSET VIEWS

  # sometimes multiple card formgroups, sometimes just one
  view :content_formgroups, unknown: true, cache: :never do
    wrap_with :fieldset, edit_slot, class: classy("card-editor", "editor")
  end

  view :name_formgroup do
    formgroup "Name", input: "name", help: false do
      raw name_field
    end
  end

  # single card content formgroup, labeled with "Content"
  view :content_formgroup, unknown: true, cache: :never do
    wrap_content_formgroup { content_field }
  end

  view :edit_in_form, cache: :never, perms: :update, unknown: true do
    reset_form
    @in_multi_card_editor = true
    edit_slot
  end

  view :conflict_tracker, cache: :never, unknown: true do
    return unless card&.real?

    card.last_action_id_before_edit = card.last_action_id
    hidden_field :last_action_id_before_edit, class: "current_revision_id"
  end

  def wrap_content_formgroup &block
    formgroup "Content", input: :content, help: false,
                         class: classy("card-editor"), &block
  end

  def button_formgroup &block
    wrap_with :div, class: "form-group #{classy 'button-form-group'}", &block
  end

  def name_field
    # value needed because otherwise gets wrong value if there are updates
    text_field :name, value: card.name, autocomplete: "off"
  end

  def content_field
    with_nest_mode :normal do
      # by changing nest mode to normal, we ensure that editors (eg image
      # previews) can render core views.
      output [_render_conflict_tracker, _render_input]
    end
  end

  # SAMPLE editor view for override
  # view :input do
  #   text_area :content, rows: 5, class: "d0-card-content"
  # end

  def edit_slot
    case
    when inline_nests_editor?  then _render_core
    when multi_card_editor?    then multi_card_edit(true)
    when in_multi_card_editor? then editor_in_multi_card
    else                            single_card_edit_field
    end
  end

  # test: render nests within a normal rendering of the card's content?
  # (as opposed to a standardized form)
  def inline_nests_editor?
    voo.input_type == :inline_nests
  end

  # test: are we opening a new multi-card form?
  def multi_card_editor?
    voo.structure || voo.edit_structure || # structure configured in voo
      card.structure ||                    # structure in card rule
      edit_fields?                         # list of fields in card rule
  end

  # override and return true to optimize
  def edit_fields?
    edit_fields.present?
  end

  # test: are we already within a multi-card form?
  def in_multi_card_editor?
    @in_multi_card_editor.present?
  end

  def single_card_edit_field
    if voo.show?(:type_formgroup) || voo.show?(:name_formgroup)
      _render_content_formgroup # use formgroup for consistency
    else
      editor_wrap(:content) { content_field }
    end
  end

  def editor_in_multi_card
    add_junction_class
    wrap_editor_in_multi_card do
      [content_field, (form.hidden_field(:type_id) if card.new_card?)]
    end
  end

  def wrap_editor_in_multi_card &block
    return yield if input_type == :hidden

    formgroup render_title,
              input: "content", help: true, class: classy("card-editor"),
              &block
  end

  def multi_card_edit fields_only=false
    field_configs = edit_field_configs fields_only
    return structure_link if field_configs.empty?

    field_configs.map do |name, options|
      nest name, options || {}
    end.join "\n"
  end

  def structure_link
    # LOCALIZE
    structured = link_to_card card.structure_rule_card, "structured"
    "<label>Content</label>"\
    "<p><em>Uneditable; content is #{structured} without nests</em></p>"
  end

  # # @param [Hash|Array] fields either an array with field names and/or field
  # # cards or a hash with the fields as keys and a hash with nest options as
  # # values
  # def process_edit_fields fields
  #   fields.map do |field, opts|
  #     field_nest field, opts
  #   end.join "\n"
  # end
  # ###

  # If you use field cards to render a form for a new card
  # then the field cards should be created on the new card not the existing
  # card that build the form

  def form
    @form ||= inherit(:form) || new_form
  end

  def new_form
    @form_root = true unless parent&.form_root
    instantiate_builder(form_prefix, card, {})
  end

  def reset_form
    @form = new_form
  end

  def form_prefix
    case
    when explicit_form_prefix          then explicit_form_prefix # configured
    when simple_form?                  then "card"               # simple form
    when parent.card.name == card.name then parent.form_prefix   # card nests self
    else                                    edit_in_form_prefix
    end
  end

  def simple_form?
    form_root? || !form_root || !parent
  end

  def edit_in_form_prefix
    "#{parent.form_prefix}[subcards][#{card.name.from form_context.card.name}]"
  end

  def explicit_form_prefix
    inherit :explicit_form_prefix
  end

  def form_context
    form_root? || !form_root ? self : parent
  end

  def form_root?
    @form_root == true
  end

  def form_root
    return self if @form_root

    parent ? parent.form_root : nil
  end

  def card_form action, opts={}
    @form_root = true
    hidden = hidden_form_tags action, opts
    form_for card, card_form_opts(action, opts) do |cform|
      @form = cform
      hidden + output(yield(cform))
    end
  end

  def hidden_form_tags _action, opts
    success = opts.delete :success
    success_tags success
  end

  # @param action [Symbol] :create or :update
  # @param opts [Hash] html options
  # @option opts [Boolean] :redirect (false) if true form is no "slotter"
  def card_form_opts action, opts={}
    url, action = card_form_url_and_action action
    html_opts = card_form_html_opts action, opts
    form_opts = { url: url, html: html_opts }
    form_opts[:remote] = true unless html_opts.delete(:redirect)
    form_opts
  end

  def card_form_html_opts action, opts={}
    add_class opts, "card-form"
    add_class opts, "slotter" unless opts[:redirect] || opts[:no_slotter]
    add_class opts, "autosave" if action == :update
    interpret_main_success_opts opts
    opts
  end

  def interpret_main_success_opts opts
    return unless (hash = opts.delete :main_success)

    opts["data-main-success"] = JSON hash
  end

  def card_form_url_and_action action
    case action
    when Symbol then [path(action: action), action]
    when Hash   then [path(action), action[:action]]
      # for when non-action path args are required
    else
      raise Card::Error, "unsupported #card_form_url action: #{action}"
    end
  end

  def editor_wrap type=nil, &block
    html_class = "editor"
    html_class << " #{type}-editor" if type
    wrap_with :div, class: html_class, &block
  end

  # FIELD VIEWS

  def add_junction_class
    return unless card.name.compound?

    class_up "card-editor", "RIGHT-#{card.name.tag_name.safe_key}"
  end
end