08 Jan, 2009

Published at 05:56PM

Tagged with forms, helpers, plugin, programming, and rails

This post has 11 comments

form_assistant plugin updates

The changes made to my form_assistant plugin warrant a new post. They’re somewhat drastic when compared to how it worked originally.

General Usage

To use the form_assistant randomly in your application, take this approach:

1
2
3
<% form_assistant_for @project do |form| %>
  // typical form_for stuff
<% end %>

That’s the only thing you need to know. There’s no longer a separate helper for the inline errors, since the new version uses templates now.

If you’d like to make it the default in your application, take this approach:

1
ActionView::Base.default_form_builder = RPH::FormAssistant::FormBuilder

That would allow you to use form_for as you normally would.

Templates

The most notable change (I think) is the use of templates. Now, each rendered element has a corresponding template. The naming (by default) is the name of the helper. For example:

1
2
3
4
5
# renders app/views/forms/_text_field.html.erb
<%= form.text_field :name %>

# renders app/views/forms/_text_area.html.erb
<%= form.text_area :description %>

And so on. Of course, you can override the template for those special cases by passing a :template option.

1
2
# renders app/views/forms/_custom_template.html.erb
<%= form.text_field :name, :template => 'custom_template' %>

This is great because you can keep all of your form presentation in one area. It keeps them DRY.

It’s worth noting the variables that are automatically made available to each template (see line 90 of form_assistant.rb).

1
2
3
4
5
6
7
8
locals = {
  :element => element, 
  :label => label, 
  :errors => errors, 
  :tip => tip, 
  :helper => helper, 
  :required => required
}

To get started with basic templates, run this command from your project root:

1
$ rake form_assistant:install

That will create a /forms directory under app/views and put some default forms in there. Modify them at your own will (it’s recommended, actually).

Labels

Labeling form fields is a tedious task, but it’s important, too. Here’s how you can work with labels using the form_assistant:

1
2
3
4
5
6
<%= form.text_field :title, :label => 'Project Title' %>
<%= form.text_field :title, :label_text => 'Project Title' %>
<%= form.text_field :title, :label_class => 'required' %>
<%= form.text_field :title, :label_id => 'dom_id' %>
<%= form.text_field :title, :label => { :text => 'Project Title', :id => 'dom_id', :class => 'required' } %>
<%= form.text_field :title, :label => false %>

If you don’t do anything, the label will be a humanized version of the field name by default.

Defaults and Configuration

There are a few things you can customize that will change the way the form_assistant behaves, globally.

1
2
3
4
5
6
# config/initializers/form_assistant.rb

RPH::FormAssistant::FormBuilder.ignore_templates = true # defaults to false
RPH::FormAssistant::FormBuilder.ignore_labels = true    # defaults to false
RPH::FormAssistant::FormBuilder.ignore_errors = true    # defaults to false
RPH::FormAssistant::FormBuilder.template_root = '...'   # defaults to app/views/forms

Examples and Bonus Features

Using fields_for alone won’t automatically give you form_assistant niceties. You must call it in the context of the form object, and then it will work as expected.

1
2
3
4
5
<% form_assistant_for @project do |form| %>
  <% form.fields_for :tasks do |task_fields| %>
    <%= task_fields.text_field :name %>
  <% end %>
<% end %>

You can set a required flag (maybe used for a CSS class or a ”*” or something) by simply passing it:

1
<%= form.text_field :title, :required => true %>

There are a few other things, such as easy cancel links:

1
2
3
4
5
6
7
8
9
10
<%= form.cancel %>

<span class="cancel">
  <a href="/wherever/the/user/came/from">Cancel</a>
</span>

# other options:
<%= form.cancel 'Go Back' %>
<%= form.cancel 'Nevermind', :path => some_path %>
<%= form.cancel 'Go Back', :attrs => { :class => 'go-back' } %>

See for yourself how easy it is to add your own custom helpers to form_assistant.

Also, make sure you’re aware of partial and fieldset, too, as they’re extremely useful (thanks, Chris).

See the Docs

It’s seriously advised that you go through the README, as I haven’t quite covered everything in this post. It (the README) will serve as the most up-to-date documentation for the time being.

Conclusion

Hopefully this post will take the place of the out-dated one, and assist any confusion thereof. I personally think forms are the most tedious thing to deal with in a Rails application, and this plugin has severely improved my code.

If you don’t want to use my plugin, you should at least explore the options for creating your own—it’s worth the time and the effort, I promise.

As expected, you can find it on GitHub. Enjoy.

Comments

Danny Wednesday, 11 Mar, 2009 Posted at 08:23AM

Hi!

Great update.
I do have one suggestion:

It is possible to set an empty error on an activerecord field like this:
@user.errors.add(:email, "")

I want to do this because for example if the login fails i give an error on base instead of a specific field.
I do set the empty errors so the fields get market as error.

Now in your has_errors? method you check if a field has errors by calling object.errors[field].blank?

I suggest you change this to object.errors[field].nil? so you support empty errors.
Then i can do something like this in my template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="form-row<%= (errors.nil? ? '' : ' form-error-row') %>">
  <div class="label">
    <%= label %>
    <% if tip %>
      <span class="tip"><%= tip %></span>
    <% end %>
  </div>
  <div class="field">
    <%= element %>
    <% unless errors.blank? %>
      <div class="form-helper form-error"><%= errors %></div>
    <% elsif required %>
      <div class="form-helper required"><%= t("forms.required_field") %></div>
    <% end %>
  </div>
</div>

Thanks

Zac Zheng Thursday, 17 Dec, 2009 Posted at 11:40AM

I use your plugin regularly and love it for its utility.

I sometimes come to a situation where the whole form is using assitant_form but I would like some fields NOT to be. This happens when I need more control.

I am having difficulty achieving this. I have tried this for fields that should not use assistant_form:
- f.fields_for :web_page, @web_page, :builder => ActionView::Base.default_form_builder do |ff|

or :builder => nil

But neither works and I’ stumped. Can you offer any advice on this?

Ryan Thursday, 17 Dec, 2009 Posted at 02:55PM

@Zac Zheng -

Yes, there is a way around this. Try passing template => false.

1
<%= f.text_field :title, :template => false %>

Let me know if that didn’t fix your problem.

As for the fields_for situation, you could try neglecting the form object (f in your case). Something like:

1
2
3
<% fields_for :site, :web_page, @web_page do |ff| %>
  ...
<% end %>

I think that would revert back to its normal behavior, as the form assistant builder lies in the form object, so if you don’t use it, fields_for is none the wiser.

Paul Monday, 25 Jan, 2010 Posted at 01:21PM

Quick q:

Can I add a spinner? I’m using form_assistant to upload large files. Currently, I don’t have anything to show the user that the request is still in process.

Thanks!

Ryan Monday, 25 Jan, 2010 Posted at 01:37PM

@Paul -

That should actually be something that is handled via javascript. If you’re using jQuery, you could do something like this:

1
2
3
$('input[type=submit]').click(function() {
  $(this).val('Working...')
})

That would change the button to say “Working…” when it is clicked. Or, if you’re using Ajax and looking for something more universal, like a loading graphic, you could do something like this:

1
2
3
4
$.ajaxSetup({ 
  'beforeSend': function(xhr) { $('#loading').show() },
  'complete': function(xhr) { $('#loading').hide() }
})

That would show an element with a DOM id of ‘loading’ anytime an Ajax request was performed – so you could put whatever you wanted in there, a message, a spinner, etc. You could also set it up with content_for if you wanted custom loading messages per view.

Let me know if you need further help with your spinner issue, but I’m sorry to say that it’s not something that form_assistant should be concerned with.

Thanks!

Paul Tuesday, 26 Jan, 2010 Posted at 10:58AM

Thanks for the response. I’ll dig into this later today and let you know how it goes.

Nik Wednesday, 02 Jun, 2010 Posted at 06:48PM

I have been reading about your plugin and I am really happy with it. But your documentation talks about a form.submission and a form.cancel

I cannot get either one to work, it appears they have been removed. If so why? It would be extremely useful to me to be able to write form.submit or form.submission and have my submit button embedded in a template too.

How would I achieve that?

Ryan Wednesday, 02 Jun, 2010 Posted at 10:07PM

Nik -

I’m sorry about that, this post is rather out-of-date. Those two methods (form.submission() and form.cancel()) no longer exist. The reason is because those “helpers” aren’t really what the form_assistant is about. Its main concern is creating form fields, and I got a little carried away building features that were out of scope. So I yanked them out (actually, Chris did). You can find accurate documentation from the README.

However, that doesn’t help you solve your problem. If you’re really ambitious, you can checkout the commit that removed all of that stuff and add it back in. But specifically, here’s an example for how you might just add in the cancel() helper:

1
2
3
4
5
6
7
8
def cancel(*args)         
  options = args.extract_options!
  text = options.delete(:text) || (args.first if args.first.is_a?(String)) || 'Cancel'
  path = options.delete(:path) || @template.request.referrer || @template.send("#{@object_name.to_s.pluralize}_path")         
  attrs = { :class => 'cancel' }.merge!(options.delete(:attrs) || {})

  content_tag(:span, @template.link_to(text, path, options), attrs)
end

If you stick that in the public section of form_assistant.rb, it should be available in your form-assisted forms. But if you get into adding a bunch of helpers, you should probably stick them in a separate module and include it in (like I did before). That’s a bit cleaner, and then if/when you realize those don’t belong in the plugin, it’s easy to pull out :-)

Let me know if you have any other questions. Hope that helps.

Nik Thursday, 03 Jun, 2010 Posted at 10:55AM

Thanks Ryan, That will get me started on what I want to achieve. I would agree that the ‘cancel’ piece should be in a different plugin. However, having the ability to put the submit button in a template would be a really good addition to this plugin. Maybe I will make a fork of this with the submit template added in.

This is what I would like to do:

1
2
3
4
5
6
<% form_assistant_for @post do |form| %>
  <%= form.error_messages %>
  <%= form.text_field :title %>
  <%= form.text_area :body %>
  <%= form.submit 'Save Changes' %>
<% end %>

Would output something like this: (based on my customized templates)

1
2
3
4
5
6
7
8
9
10
11
12
13
<form method='post' action='/posts/new'>
  <div class='form-input text-'>
    <label for='post[title]'>Title</label>
    <input type='text' name='post[title]' />
  </div>
  <div class='form-input textarea'>
    <label for='post[body]'>Body</label>
    <textarea name='post[body'></textarea>
  </div>
  <div class='form-input submit'>
    <input type='submit' value='Save Changes' />
  </div>
</form>

Notice the submit button is placed inside a div which would be the template that I had specified for it. This would allow me to float the divs containing the form elements if I chose to.

Anyway, I will checkout the source and see if I can add this functionality. Let me know what you think.

Thanks again

Ryan Thursday, 03 Jun, 2010 Posted at 02:16PM

I do understand your desire for that. I do something similar on all of my forms. Here’s what I currently do (in application_helper.rb):

1
2
3
def submission(options = {}, &block)
  concat content_tag(:p, capture(&block), { :class => 'submission' }.merge!(options))
end

That allows me to do things like:

1
2
3
<% submission do %>
  <%= form.submit "Save Changes" %> or <%= form.cancel %>
<% end %>

Or if you wanted to pass options to the submission wrapper, something like:

1
2
3
<% submission :class => 'form-input submit' do %>
  <%= form.submit "Save Changes" %> or <%= form.cancel %>
<% end %>

Just an option.

In your example, though, you’re wanting the form.submit to be a native feature if the form assistant plugin. The way the form_assistant works is it overrides all of Rails’ form helpers and redefines them to use the templates. So if you want the submit to be a part of that list, it’s pretty simple. Just add it to the list of form helpers, like so (look for ‘submit’ at the end of the line):

1
2
3
4
5
FORM_HELPERS = [
  ActionView::Helpers::FormBuilder.field_helpers + 
  %w(date_select datetime_select time_select collection_select select country_select time_zone_select submit) - 
  %w(hidden_field label fields_for)
].flatten.freeze

And then in your app/views/forms/ directory, just add a _submit.html.erb template and the field will automatically use that template any time you’re calling form.submit. And in there, you can then wrap your submit in any div you want, along with a cancel link. Look at the other form templates to see all of the things that are made available – you’ll mostly be concerned with the element variable, though (that’s the actual HTML item).

Just to be clear, your _submit.html.erb template might look like this:

1
2
3
<div class="form-input submit">
  <%= element %> or <span class="cancel"><%= link_to 'Cancel', request.referrer %></span>
</div>

Hopefully that makes sense. Let me know if you run into trouble. I had the submit helper in the list of fields at some point, but I took it out. I’m having a hard time remembering why, though :-)

Nik Thursday, 03 Jun, 2010 Posted at 04:22PM

That is exactly what I ended up doing, thanks for your replies.

It took me a little bit to figure out that ‘submit’ was not part of ActionView::Helpers::FormBuilder.field_helpers though.

It is working exactly as I expected now. Thank you.

  • Might i suggest adding it back in if you release a new update?

BTW, This plugin is very useful. ;-)

Do you have something to say about this post?
Retype the image to the right Spam Hint: Are You Human? Textile Formatting Tips

or

Ryan Heath | Site Management A Ruby on Rails production.

This site is a Formed Function. Formed Function LLC | @formedfunction | Get in Touch