Custom Form Helpers

Custom form helpers

Yes, you can add to the FormBuilder class and get access to the object passed into the form_for. I've done this for a lot of things: dates, times, measurements, etc. Heres an example:

class ActionView::Helpers::FormBuilder
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
include ActionView::Helpers::CaptureHelper
include ActionView::Helpers::AssetTagHelper

# Accepts an int and displays a smiley based on >, <, or = 0
def smile_tag(method, options = {})
value = @object.nil? ? 0 : @object.send(method).to_i
options[:id] = field_id(method,options[:index])
smiley = ":-|"
if value > 0
smiley = ":-)"
elsif smiley < 0
smiley = ":-("
end
return text_field_tag(field_name(method,options[:index]),options) + smiley
end

def field_name(label,index=nil)
output = index ? "[#{index}]" : ''
return @object_name + output + "[#{label}]"
end

def field_id(label,index=nil)
output = index ? "_#{index}" : ''
return @object_name + output + "_#{label}"
end

end

Which you can use like this:

<% form_for @quiz do |f| %>
<%= f.smile_tag(:score) %>
<% end %>

There are some instance variables created by Rails that you can access in these helper methods:

  • @object - the model object specified by the form
  • @object_name - the class name of the object
  • @template - I think its an instance of the ActionView, you can possibly bypass all the includes I added by calling methods on the template. Haven't tried that yet.
  • @options - options passed to the FormBuilder when its created by the form_for call

I wrote the field_id and field_name methods to create these attributes on the HTML input elements the same way the regular helpers do, I'm sure there is a way to tie into the same methods that Rails uses, but I haven't found it yet.

The sky is the limit on what you can do with these helper methods, they simply return strings. You can create entire HTML tables or pages in one, but you better have a good reason to.

This file should be added in the app/helpers folder

Rails: understanding custom form helper

form.radio_button helper returns a string and I18n.t too returns a string. So, you can concatenate them.

More details how form tag is generated

This is a code of radio_button:

https://github.com/casunlight/rails/blob/master/actionview/lib/action_view/helpers/form_helper.rb

def radio_button(object_name, method, tag_value, options = {})
Tags::RadioButton.new(object_name, method, self, tag_value, options).render
end

Look at implementation of render method

https://github.com/casunlight/rails/blob/master/actionview/lib/action_view/helpers/tags/radio_button.rb#L20

def render
options = @options.stringify_keys
options["type"] = "radio"
options["value"] = @tag_value
options["checked"] = "checked" if input_checked?(object, options)
add_default_name_and_id_for_value(@tag_value, options)
tag("input", options)
end

Tag helper generate html tag and return his as html safed string:

https://github.com/casunlight/rails/blob/master/actionview/lib/action_view/helpers/tag_helper.rb#L67

def tag(name, options = nil, open = false, escape = true)
"<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
end

Trouble setting up Rails custom form helper method

I went back to the docs for check_box_tag, and understood it a bit more. I ended up going with this:

module FormHelper
def wrapped_check_box_tag(name, value=1, checked=false, options={})
raw "<div class='checkbox-wrapper'>" + \
check_box_tag(name, value, checked, options) + \
"<span class='checkbox-checks'></span>" + \
"</div>"
end

class ActionView::Helpers::FormBuilder
include FormHelper
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper

def wrapped_check_box(name, options = {})
wrapped_check_box_tag("#{@object_name}[#{name.to_s}]", options[:value], options[:checked], options)
end
end
end

This now makes a global method, as well as a form helper available:

<%= wrapped_check_box_tag :accepts_terms %>

<%= f.wrapped_check_box :receives_updates, { checked: true } %>

If anyone has any suggestions, I'm all ears!

How to write a custom form helper template for dynamically generated content?

The Twitter Bootstrap structure needs to be accurate.

Wrap the btn-group class around the inputRadioGroup helper like so:

<div class="btn-group" data-toggle="buttons">
@helper.inputRadioGroup(
answerRadioForm("Answer"),
options = answerList.map(answer => answer.answerID.toString -> answer.answerText),
'_label -> "Answer",
'_error -> answerRadioForm("answerID").error.map(_.withMessage("select answer")))
</div>

Then replace the template with:

@(elements: helper.FieldElements)

<label class="btn btn-primary">
@elements.input @elements.label
</label>

<span class="errors">@elements.errors.mkString(", ")</span>
<span class="help">@elements.infos.mkString(", ")</span>

In general, perhaps it'd be of interest another way of doing it. When you use fooForm("myField"...), you can use fooForm("myField[@i]"...) in a for loop where @i is a counter going from 0 to however many inputs there are. Then you can yourself sketch out the full HTML instead of using implicit values.

By the way, the documentation with the Scala version about all this has lots more information than the Java version. See here. It has more information on inputRadioGroup than the Java version of the documentation but still very useful reading for better understanding of all this.

Some Play Bootstrap plugin has code available on GitHub that is also useful reading especially as it uses implicit values also.

UPDATE

Here's a couple of GitHub projects showing how to achieve this:

  • with Scala version of Play: https://github.com/bjfletcher/play-bootstrap-radio-group
  • with Java version of Play: https://github.com/bjfletcher/play-java-bootstrap-radio-group

Screenshot of the result:

Sample Image

How to register custom form view helper in Zend Framework 3

Taking an example from an active project at the company I work at. We also have some default ZF3 Form ViewHelpers overwritten with our own to interface with the front-end framework. Theme name is "Alpha" (I think ;-) )

We use the following:

'view_helpers' => [
// other stuff
'invokables' => [
'Zend\Form\View\Helper\FormCollection' => AlphaCollection::class,
'Zend\Form\View\Helper\Form' => AlphaForm::class,
'Zend\Form\View\Helper\FormRow' => AlphaRow::class,
'Zend\Form\View\Helper\FormSelect' => AlphaSelect::class,
],
],

View helper itself:

// Namespace + use statements
class AlphaCollection extends FormCollection
{
public function __construct()
{
parent::setWrapper('<div class="alpha-form-collection">%2$s%1$s%3$s</div>');
}

/**
* @param \Zend\Form\ElementInterface $element
* @param null $labelPosition
* @return string
*/
public function render(ElementInterface $element, $labelPosition = null)
{
$markup = parent::render($element, $labelPosition);

$classes = 'input-field col s12 alpha-fieldset';

if($element instanceof Collection)
{
$classes .= ' alpha-fieldset-collection';
}

$prepend = '<div class="' . $classes . '">';
$append = '</div>';

return $prepend . $markup . $append;
}
}

So in essence, we're not so much creating our own ViewHelpers so much as changing the ones provided by Zend Framework 3. Because we're just "updating" existing ones, we don't have to create new Factories (no additional requirements).

Zend Framework has registered ViewHelpers with invokable names (so you can do $this->formRow(...) or $this->formSelect(...). We've just hijacked their configuration and replaced the class we need with our own. That way, when we have a form generated completely (<?= $this->form($form) ?>), ZF does all the work for us.

Implementation in a .phtml:

<!-- Internally uses the invokables we've modified, so this is all we need to do :) -->
<?= $this->form($form) ?>

To make the configuration a bit more future proof, I think you can replace the string invokable with a FQCN nowadays (have not tested this (yet))

Custom Form Builder in Rails 5.1

Shouldn't it be

  <%= f.radio_button(:admin, "child") %>

instead of

  <%= f.div_radio_button(:admin, "child") %>

UPDATE

Sorry I hurriedly answered this yesterday. Looking at it again you seem to be missing the builder option of the form so you are still using the ActionView::Helpers::FormBuilder instead of your custom form builder class.

<%= form_for @person, :builder => MyFormBuilder do |f| %>
I am a child: <%= f.div_radio_button(:admin, "child") %>
I am an adult: <%= f.div_radio_button(:admin, "adult") %>
<% end -%>

The snippet above is from the docs

So what you need is probably this

<%= form_with model: @user, url: users_path, method: :post, builder: CustomFormBuilder do |f|  %>
...
<%= f.div_radio_button(:admin, "child") %>
<% end %>


Related Topics



Leave a reply



Submit