Rails 6 create custom formtastic input based on existing one

771 Views Asked by At

Inside of my ActiveAdmin Rails6 app I've got below partial which replaces the standard input by editor.js (there is some JS magic behind, not relevant to this question). The partial and render look like below:

# _editor.html.erb
<%= f.input field, as: :hidden, input_html: { id: :editor_field } %>
<div style='width: 100%; background: white;' id="editorjs"></div>

# example of a parital call for field :body
<%= render 'admin/editor_js', f: f, field: :body %>

Because ActiveAdmin is based on formatic gem instead of this partial I want to create and use custom input based on :text field. I was trying to do something like below.

module CustomInputs
  class EditorJsInput < Formtastic::Inputs::TextInput
    def input_html_options
      super.merge(input_html: { id: 'editor_field' }).merge(as: :hidden)
    end
  end
end

(as: :hidden) is not working and no idea how to add this empty div at the end <div style='width: 100%; background: white;' id="editorjs"></div> which is quite crucial.

1

There are 1 best solutions below

4
Allison On

as: :hidden is not an input_html_option, it's an input style/type that maps the input to the Inputs::HiddenInput type; all it really does is render the input field as a hidden list item. Additionally, the way you're overriding input_html_options is not correct:

# Let's say this is a line in your form input:
input_html: { value: 'Eat plants.' } 

# Your code is changing `input_html_options` to be the same as if you'd 
# included this in your form input declaration, which doesn't make sense:
input_html: { 
  value:      'Eat plants.', 
  input_html: { id: 'editor_field' }, 
  as:         :hidden
}

Please review the docu and source on the methods you are trying to override:

An overridden input_html_options method would be something like this if you're looking to override the existing ID:

# Don't recommend
def input_html_options
  # Note: this is dangerous because the 'id' attribute should be unique
  # and merging it here instead of passing it in the field's `input_html` 
  # hash forces every input of this type to have the same id
  super.merge(id: 'editor_field')
end

Presuming that you're really looking to reliably know the ID so some JS can find the element and swap it out, you could either specify it in the input_html hash when declaring your form input or, more cleanly, simply use the autogenerated ID. Generally the ID is the the underscore-separated form object root key + the attribute name; so if your form object is coffee: { name: 'Goblin Sludge', origin_country: 'Columbia' }, I believe the default is for f.input :name to render with id='coffee_name' and f.input :origin_country to render with id='coffee_origin_country'. This is stuff that you can easily figure out by using the devtools inspector on the rendered form.

It seems like you really are looking to override to_html. You need something like this:

# app/input/editor_js_input.rb
class EditorJsInput < Formtastic::Inputs::TextInput
  def to_html
    input_wrapping do
      builder.hidden_field(method, input_html_options)
    end
  end
end

# Variation that creates a div
class EditorJsInput < Formtastic::Inputs::TextInput
  def to_html
    input_wrapping do
      builder.content_tag(
        :div, 
        '', 
        style: 'width: 100%; background: white;', 
        id: "#{input_html_options[:id]}_editor"
      ) << builder.hidden_field(method, input_html_options)
    end
  end
end
# Example of what this would generate:
# "<div style=\"width: 100%; background: white;\" id=\"coffee_name_editor\"></div><input maxlength=\"255\" id=\"coffee_name\" value=\"\" type=\"hidden\" name=\"coffee[name]\" />"


# PARTIAL
# This will render the div you have above:
div style: 'width: 100%; background: white;', id: 'editorjs'

# This (or a similar variant of form declaration) will render the form
active_admin_form_for resource do |f|
  f.inputs do
    f.input :name, 
      input_html: { value: f.object.name }, 
      as: :editor_js
    f.input :origin_country, 
      input_html: { value: f.object.origin_country }, 
      as: :editor_js
  end
end

This should build the input for f.object.name as something like <input id="coffee_name" type="hidden" value="Goblin Sludge" name="coffee[name]"> (see FormBuilder#hidden_field

Additional thoughts:

  • Try using something like Pry to put a binding inside these methods to get a better understanding of what they look like and how they work. Note that you will need to reboot your server for it to load changes to overridden methods in custom input classes.
  • Read the documentation referenced above and the Formtastic README; there is a lot of really accessible info that would have helped you here. You could also have learned how to render a div from ActiveAdmin's docu here (as well as other pages at activeadmin.info that give examples of styled elements)
  • Evaluate whether you need to implement a Custom FormBuilder