Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into sexy_specs

  • Loading branch information...
commit e7426a982aa44af0e1bd87f9f160bfb2fe59420a 2 parents 0426eb6 + f72a6f3
@haines haines authored
Showing with 2,208 additions and 161 deletions.
  1. +1 −0  .gitignore
  2. +1 −1  Appraisals
  3. +39 −36 README.textile
  4. +16 −3 app/assets/stylesheets/formtastic.css
  5. +1 −1  formtastic.gemspec
  6. +1 −1  gemfiles/rails-3.2.gemfile
  7. +9 −0 lib/formtastic.rb
  8. +11 −0 lib/formtastic/actions.rb
  9. +156 −0 lib/formtastic/actions/base.rb
  10. +72 −0 lib/formtastic/actions/button_action.rb
  11. +17 −0 lib/formtastic/actions/buttonish.rb
  12. +68 −0 lib/formtastic/actions/input_action.rb
  13. +87 −0 lib/formtastic/actions/link_action.rb
  14. +2 −0  lib/formtastic/form_builder.rb
  15. +2 −0  lib/formtastic/helpers.rb
  16. +109 −0 lib/formtastic/helpers/action_helper.rb
  17. +168 −0 lib/formtastic/helpers/actions_helper.rb
  18. +8 −0 lib/formtastic/helpers/buttons_helper.rb
  19. +5 −2 lib/formtastic/helpers/inputs_helper.rb
  20. +10 −1 lib/formtastic/inputs/select_input.rb
  21. +1 −1  lib/formtastic/version.rb
  22. +2 −2 lib/generators/templates/_form.html.erb
  23. +2 −2 lib/generators/templates/_form.html.haml
  24. +2 −0  lib/locale/en.yml
  25. +22 −0 sample/basic_inputs.html
  26. +63 −0 spec/actions/button_action_spec.rb
  27. +484 −0 spec/actions/generic_action_spec.rb
  28. +59 −0 spec/actions/input_action_spec.rb
  29. +92 −0 spec/actions/link_action_spec.rb
  30. +365 −0 spec/helpers/action_helper_spec.rb
  31. +143 −0 spec/helpers/actions_helper_spec.rb
  32. +39 −23 spec/helpers/buttons_helper_spec.rb
  33. +139 −86 spec/helpers/commit_button_helper_spec.rb
  34. +9 −1 spec/inputs/select_input_spec.rb
  35. +3 −1 spec/support/template.rb
View
1  .gitignore
@@ -4,6 +4,7 @@ coverage
coverage.data
pkg
*~
+*.swp
*watchr.rb
log/*
.rvmrc
View
2  Appraisals
@@ -7,5 +7,5 @@ appraise 'rails-3.1' do
end
appraise 'rails-3.2' do
- gem 'rails', '~> 3.2.0.rc1'
+ gem 'rails', '~> 3.2.0.rc2'
end
View
75 README.textile
@@ -40,8 +40,9 @@ One day, I finally had enough, so I opened up my text editor, and wrote a DSL fo
<%= author_form.input :last_name %>
<% end %>
- <%= f.buttons do %>
- <%= f.commit_button %>
+ <%= f.actions do %>
+ <%= f.action :submit, :as => :button %>
+ <%= f.action :cancel, :as => :link %>
<% end %>
<% end %>
@@ -139,27 +140,27 @@ h2. Usage
Forms are really boring to code... you want to get onto the good stuff as fast as possible.
-This renders a set of inputs (one for _most_ columns in the database table, and one for each ActiveRecord @belongs_to@-association), followed by a submit button:
+This renders a set of inputs (one for _most_ columns in the database table, and one for each ActiveRecord @belongs_to@-association), followed by default action buttons (an input submit button):
<pre>
<%= semantic_form_for @user do |f| %>
<%= f.inputs %>
- <%= f.buttons %>
+ <%= f.actions %>
<% end %>
</pre>
This is a great way to get something up fast, but like scaffolding, it's *not recommended for production*. Don't be so lazy!
-To specify the order of the fields, skip some of the fields or even add in fields that Formtastic couldn't infer. You can pass in a list of field names to @inputs@ and list of button names to @buttons@:
+To specify the order of the fields, skip some of the fields or even add in fields that Formtastic couldn't infer. You can pass in a list of field names to @inputs@ and list of action names to @actions@:
<pre>
<%= semantic_form_for @user do |f| %>
<%= f.inputs :title, :body, :section, :categories, :created_at %>
- <%= f.buttons :commit %>
+ <%= f.actions :submit, :cancel %>
<% end %>
</pre>
-You probably want control over the input type Formtastic uses for each field. You can expand the @inputs@ and @buttons@ to block helper format and use the @:as@ option to specify an exact input type:
+You probably want control over the input type Formtastic uses for each field. You can expand the @inputs@ and @actions@ to block helper format and use the @:as@ option to specify an exact input type:
<pre>
<%= semantic_form_for @post do |f| %>
@@ -170,8 +171,9 @@ You probably want control over the input type Formtastic uses for each field. Yo
<%= f.input :categories %>
<%= f.input :created_at, :as => :string %>
<% end %>
- <%= f.buttons do %>
- <%= f.commit_button %>
+ <%= f.actions do %>
+ <%= f.action :submit, :as => :button %>
+ <%= f.action :cancel, :as => :link %>
<% end %>
<% end %>
</pre>
@@ -191,8 +193,8 @@ If you want to customize the label text, or render some hint text below the fiel
<%= f.input :categories, :required => false %>
<%= f.input :created_at, :as => :string, :label => "Publication Date", :required => false %>
<% end %>
- <%= f.buttons do %>
- <%= f.commit_button %>
+ <%= f.actions do %>
+ <%= f.action :submit %>
<% end %>
<% end %>
</pre>
@@ -211,7 +213,7 @@ Nested forms are also supported (don't forget your models need to be setup corre
<%= f.semantic_fields_for :author do |author| %>
<%= author.inputs :first_name, :last_name, :name => "Author" %>
<% end %>
- <%= f.buttons %>
+ <%= f.actions %>
<% end %>
</pre>
@@ -221,7 +223,7 @@ Or the Formtastic way with the @:for@ option:
<%= semantic_form_for @post do |f| %>
<%= f.inputs :title, :body, :created_at %>
<%= f.inputs :first_name, :last_name, :for => :author, :name => "Author" %>
- <%= f.buttons %>
+ <%= f.actions %>
<% end %>
</pre>
@@ -231,7 +233,7 @@ When working in has many association, you can even supply @"%i"@ in your fieldse
<%= semantic_form_for @post do |f| %>
<%= f.inputs %>
<%= f.inputs :name => 'Category #%i', :for => :categories %>
- <%= f.buttons %>
+ <%= f.actions %>
<% end %>
</pre>
@@ -244,7 +246,7 @@ If you have more than one form on the same page, it may lead to HTML invalidatio
<%= f.input :body %> # id="cat_form_post_body"
<%= f.input :created_at %> # id="cat_form_post_created_at"
<% end %>
- <%= f.buttons %>
+ <%= f.actions %>
<% end %>
</pre>
@@ -256,18 +258,19 @@ Customize HTML attributes for any input using the @:input_html@ option. Typicall
<%= f.input :title, :input_html => { :size => 10 } %>
<%= f.input :body, :input_html => { :class => 'autogrow', :rows => 10, :cols => 20, :maxlength => 10 } %>
<%= f.input :created_at, :input_html => { :disabled => true } %>
+ <%= f.input :updated_at, :input_html => { :readonly => true } %>
<% end %>
- <%= f.buttons %>
+ <%= f.actions %>
<% end %>
</pre>
-The same can be done for buttons with the @:button_html@ option:
+The same can be done for actions with the @:button_html@ option:
<pre>
<%= semantic_form_for @post do |f| %>
...
- <%= f.buttons do %>
- <%= f.commit_button :button_html => { :class => "primary", :disable_with => 'Wait...' } %>
+ <%= f.actions do %>
+ <%= f.action :submit, :button_html => { :class => "primary", :disable_with => 'Wait...' } %>
<% end %>
<% end %>
</pre>
@@ -408,6 +411,8 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
actions:
create: "Create my %{model}"
update: "Save changes"
+ reset: "Reset form"
+ cancel: "Cancel and go back"
dummie: "Launch!"
</pre>
@@ -420,8 +425,8 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
<%= f.input :body %> # => :label => "Write something...", :hint => "Write something inspiring here."
<%= f.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
<% end %>
- <%= f.buttons do %>
- <%= f.commit_button %> # => "Create my %{model}"
+ <%= f.actions do %>
+ <%= f.action :submit %> # => "Create my %{model}"
<% end %>
<% end %>
</pre>
@@ -448,8 +453,8 @@ _Note: Slightly different because Formtastic can't guess how you group fields in
<%= f.input :body, :hint => false %> # => :label => "Write something..."
<%= f.input :section, :label => 'Some section' %> # => :label => 'Some section'
<% end %>
- <%= f.buttons do %>
- <%= f.commit_button :dummie %> # => "Launch!"
+ <%= f.actions do %>
+ <%= f.action :submit, :label => :dummie %> # => "Launch!"
<% end %>
<% end %>
</pre>
@@ -469,8 +474,8 @@ If I18n-lookups is disabled, i.e.:
<%= f.input :body, :label => true %> # => :label => "Write something..."
<%= f.input :section, :label => true %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
<% end %>
- <%= f.buttons do %>
- <%= f.commit_button true %> # => "Update %{model}" (if we are in edit that is...)
+ <%= f.actions do %>
+ <%= f.action :submit, :label => true %> # => "Update %{model}" (if we are in edit that is...)
<% end %>
<% end %>
</pre>
@@ -597,27 +602,25 @@ There are none, but...
h2. How to contribute
-Please ensure that you provide appropriate spec/test coverage and ensure the documentation is up-to-date. Bonus points if you perform your changes in a clean topic branch rather than master, and if you create a pull request for your changes to be discussed and reviewed.
+* Fork the project on Github
+* Create a topic branch for your changes
+* Ensure that all tests pass (`bundle exec rake`)
+* Ensure that the changes in your branch are as atomic as possible
+* Create a pull request on Github
-Please also keep your commits *atomic* so that they are more likely to apply cleanly. That means that each commit should contain the smallest possible logical change. Don't commit two features at once, don't update the gemspec at the same time you add a feature, don't fix a whole bunch of whitespace in a file at the same time you change a few lines, etc, etc.
-
-For significant changes, you may wish to discuss your idea on the Formtastic Google group before coding to ensure that your change is likely to be accepted. Formtastic relies heavily on i18n, so if you're unsure of the impact this has on your changes, please discuss them with the group.
-
-See below for installation of a development environment.
+For significant changes, you may wish to discuss your idea on the Formtastic Google group before coding to ensure that your change is likely to be accepted. Formtastic relies heavily on i18n, so if you're unsure of the impact this has on your changes, please discuss them with the group.
h2. Google Group, Twitter, etc
Please join the "Formtastic Google Group":http://groups.google.com.au/group/formtastic, especially if you'd like to talk about a new feature, or report a bug.
-You can also "follow @formtastic on Twitter":http://twitter.com/formtastic for announcements, tutorials and awesome Formtastic links.
+You can also follow "@justinfrench":http://twitter.com/formtastic or "@formtastic":http://twitter.com/formtastic on Twitter for announcements, tutorials and links.
h2. Project Info
-Formtastic was created by "Justin French":http://www.justinfrench.com with contributions from over 100 awesome developers.
-
-Run @git shortlog -n -s@ to see the awesome.
+Formtastic was created by "Justin French":http://www.justinfrench.com with contributions from around 150 awesome developers. Run @git shortlog -n -s@ to see the awesome.
The project is hosted on Github: "http://github.com/justinfrench/formtastic":http://github.com/justinfrench/formtastic, where your contributions, forkings, comments, issues and feedback are greatly welcomed.
-Copyright (c) 2007-2010 Justin French, released under the MIT license.
+Copyright (c) 2007-2012 Justin French, released under the MIT license.
View
19 app/assets/stylesheets/formtastic.css
@@ -18,6 +18,7 @@ This stylesheet forms part of the Formtastic Rails Plugin
.formtastic fieldset,
.formtastic legend,
.formtastic input,
+.formtastic button,
.formtastic textarea,
.formtastic select,
.formtastic p {
@@ -47,6 +48,7 @@ This stylesheet forms part of the Formtastic Rails Plugin
}
.formtastic input,
+.formtastic button,
.formtastic textarea {
font-family:sans-serif;
font-size:inherit;
@@ -80,18 +82,29 @@ This stylesheet forms part of the Formtastic Rails Plugin
}
-/* BUTTONS
+/* BUTTONS & ACTIONS
--------------------------------------------------------------------------------------------------*/
-.formtastic .buttons {
+.formtastic .buttons,
+.formtastic .actions {
overflow:hidden; /* clear containing floats */
padding-left:25%;
}
-.formtastic .button {
+.formtastic .button,
+.formtastic .action {
float:left;
padding-right:0.5em;
}
+.formtastic .button_action button {
+ padding:3px 8px;
+}
+
+.formtastic .link_action a {
+ display:block;
+ padding:3px 0;
+}
+
/* INPUTS
--------------------------------------------------------------------------------------------------*/
View
2  formtastic.gemspec
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
s.add_dependency(%q<actionpack>, ["~> 3.0"])
- s.add_development_dependency(%q<rspec-rails>, ["~> 2.8.0.rc2"])
+ s.add_development_dependency(%q<rspec-rails>, ["~> 2.8.0"])
s.add_development_dependency(%q<rspec_tag_matchers>, [">= 1.0.0"])
s.add_development_dependency(%q<hpricot>, ["~> 0.8.3"])
s.add_development_dependency(%q<BlueCloth>) # for YARD
View
2  gemfiles/rails-3.2.gemfile
@@ -2,6 +2,6 @@
source :rubygems
-gem "rails", "~> 3.2.0.rc1"
+gem "rails", "~> 3.2.0.rc2"
gemspec :path=>"../"
View
9 lib/formtastic.rb
@@ -9,6 +9,7 @@ module Formtastic
autoload :HtmlAttributes
autoload :I18n
autoload :Inputs
+ autoload :Actions
autoload :LocalizedString
autoload :Localizer
autoload :Util
@@ -18,7 +19,15 @@ class UnknownInputError < NameError
end
# @private
+ class UnknownActionError < NameError
+ end
+
+ # @private
class PolymorphicInputWithoutCollectionError < ArgumentError
end
+ # @private
+ class UnsupportedMethodForAction < ArgumentError
+ end
+
end
View
11 lib/formtastic/actions.rb
@@ -0,0 +1,11 @@
+module Formtastic
+ module Actions
+ extend ActiveSupport::Autoload
+
+ autoload :Base
+ autoload :Buttonish
+ autoload :InputAction
+ autoload :LinkAction
+ autoload :ButtonAction
+ end
+end
View
156 lib/formtastic/actions/base.rb
@@ -0,0 +1,156 @@
+module Formtastic
+ module Actions
+ module Base
+ include Formtastic::LocalizedString
+
+ attr_accessor :builder, :template, :object, :object_name, :method, :options
+
+ def initialize(builder, template, object, object_name, method, options)
+ @builder = builder
+ @template = template
+ @object = object
+ @object_name = object_name
+ @method = method
+ @options = options.dup
+
+ check_supported_methods!
+ end
+
+ def to_html
+ raise NotImplementedError
+ end
+
+ def wrapper(&block)
+ template.content_tag(:li,
+ template.capture(&block),
+ wrapper_html_options
+ )
+ end
+
+ def wrapper_html_options
+ wrapper_html_options_from_options.merge(default_wrapper_html_options)
+ end
+
+ def wrapper_html_options_from_options
+ options[:wrapper_html] || {}
+ end
+
+ def default_wrapper_html_options
+ {
+ :class => wrapper_class,
+ :id => wrapper_id
+ }
+ end
+
+ def wrapper_class
+ (default_wrapper_classes << wrapper_classes_from_options).join(" ")
+ end
+
+ def default_wrapper_classes
+ ["action", "#{options[:as]}_action"]
+ end
+
+ def wrapper_classes_from_options
+ classes = wrapper_html_options_from_options[:class] || []
+ classes = classes.split(" ") if classes.is_a? String
+ classes
+ end
+
+ def wrapper_html_options_from_options
+ options[:wrapper_html] || {}
+ end
+
+ def wrapper_id
+ wrapper_id_from_options || default_wrapper_id
+ end
+
+ def wrapper_id_from_options
+ wrapper_html_options_from_options[:id]
+ end
+
+ def default_wrapper_id
+ "#{object_name}_#{method}_action"
+ end
+
+ def supported_methods
+ raise NotImplementedError
+ end
+
+ def text
+ text = options[:label]
+ text = (localized_string(i18n_key, text, :action, :model => sanitized_object_name) ||
+ Formtastic::I18n.t(i18n_key, :model => sanitized_object_name)) unless text.is_a?(::String)
+ text
+ end
+
+ def button_html
+ default_button_html.merge(button_html_from_options || {}).merge(extra_button_html_options)
+ end
+
+ def button_html_from_options
+ options[:button_html]
+ end
+
+ def extra_button_html_options
+ {}
+ end
+
+ def default_button_html
+ { :accesskey => accesskey }
+ end
+
+ def accesskey
+ # TODO could be cleaner and separated, remember that nil is an allowed value for all of these
+ return options[:accesskey] if options.key?(:accesskey)
+ return options[:button_html][:accesskey] if options.key?(:button_html) && options[:button_html].key?(:accesskey)
+ # TODO might be different for cancel, etc?
+ return builder.default_commit_button_accesskey
+ end
+
+
+ protected
+
+ def check_supported_methods!
+ raise Formtastic::UnsupportedMethodForAction unless supported_methods.include?(method)
+ end
+
+ def i18n_key
+ return submit_i18n_key if method == :submit
+ method
+ end
+
+ def submit_i18n_key
+ if new_or_persisted_object?
+ key = @object.persisted? ? :update : :create
+ else
+ key = :submit
+ end
+ end
+
+ def new_or_persisted_object?
+ object && (object.respond_to?(:persisted?) || object.respond_to?(:new_record?))
+ end
+
+ def sanitized_object_name
+ if new_or_persisted_object?
+ # Deal with some complications with ActiveRecord::Base.human_name and two name models (eg UserPost)
+ # ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
+ # if there's no i18n, which is pretty crappy. In this circumstance we want to detect this
+ # fall back (human_name == name.humanize) and do our own thing name.underscore.humanize ("User Post")
+ if object.class.model_name.respond_to?(:human)
+ sanitized_object_name = object.class.model_name.human
+ else
+ object_human_name = @object.class.human_name # default is UserPost => "Userpost", but i18n may do better ("User post")
+ crappy_human_name = @object.class.name.humanize # UserPost => "Userpost"
+ decent_human_name = @object.class.name.underscore.humanize # UserPost => "User post"
+ sanitized_object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
+ end
+ else
+ sanitized_object_name = object_name.to_s.send(builder.label_str_method)
+ end
+ sanitized_object_name
+ end
+
+ end
+ end
+end
View
72 lib/formtastic/actions/button_action.rb
@@ -0,0 +1,72 @@
+# Outputs a `<button type="submit">` or `<button type="reset">` wrapped in the standard `<li>`
+# wrapper. This is an alternative choice for `:submit` and `:reset` actions, which render with
+# `<input type="submit">` and `<input type="reset">` by default.
+#
+# @example Full form context and output
+#
+# <%= semantic_form_for(@post) do |f| %>
+# <%= f.actions do %>
+# <%= f.action :reset, :as => :button %>
+# <%= f.action :submit, :as => :button %>
+# <% end %>
+# <% end %>
+#
+# <form...>
+# <fieldset class="actions">
+# <ol>
+# <li class="action button_action" id="post_reset_action">
+# <button type="reset" value="Reset">
+# </li>
+# <li class="action button_action" id="post_submit_action">
+# <button type="submit" value="Create Post">
+# </li>
+# </ol>
+# </fieldset>
+# </form>
+#
+# @example Specifying a label with a String
+# <%= f.action :submit, :as => :button, :label => "Go" %>
+#
+# @example Pass HTML attributes down to the `<button>`
+# <%= f.action :submit, :as => :button, :button_html => { :class => 'pretty', :accesskey => 'g', :disable_with => "Wait..." } %>
+#
+# @example Access key can also be set as a top-level option
+# <%= f.action :submit, :as => :button, :accesskey => 'g' %>
+#
+# @example Pass HTML attributes down to the `<li>` wrapper (classes are appended to the existing classes)
+# <%= f.action :submit, :as => :button, :wrapper_html => { :class => 'special', :id => 'whatever' } %>
+# <%= f.action :submit, :as => :button, :wrapper_html => { :class => ['extra', 'special'], :id => 'whatever' } %>
+#
+# @option *args :label [String, Symbol]
+# Override the label text with a String or a symbol for an i18n translation key
+#
+# @option *args :button_html [Hash]
+# Override or add to the HTML attributes to be passed down to the `<input>` tag
+#
+# @option *args :wrapper_html [Hash]
+# Override or add to the HTML attributes to be passed down to the wrapping `<li>` tag
+#
+# @todo document i18n keys
+# @todo document i18n translation with :label (?)
+module Formtastic
+ module Actions
+ class ButtonAction
+ include Base
+ include Buttonish
+
+ # TODO absolutely horrible hack to work-around Rails < 3.1 missing button_tag, need
+ # to figure out something more appropriate.
+ #
+ # TODO reset_action class?
+ def to_html
+ wrapper do
+ if template.respond_to?(:button_tag)
+ template.button_tag(text, button_html)
+ else
+ template.content_tag(:button, text, button_html)
+ end
+ end
+ end
+ end
+ end
+end
View
17 lib/formtastic/actions/buttonish.rb
@@ -0,0 +1,17 @@
+module Formtastic
+ module Actions
+ module Buttonish
+
+ def supported_methods
+ [:submit, :reset]
+ end
+
+ def extra_button_html_options
+ {
+ :type => method
+ }
+ end
+
+ end
+ end
+end
View
68 lib/formtastic/actions/input_action.rb
@@ -0,0 +1,68 @@
+# Outputs an `<input type="submit">` or `<input type="reset">` wrapped in the standard `<li>`
+# wrapper. This the default for `:submit` and `:reset` actions, but `:as => :button` is also
+# available as an alternative.
+#
+# @example The `:as` can be ommitted, these are functionally equivalent
+# <%= f.action :submit, :as => :input %>
+# <%= f.action :submit %>
+#
+# @example Full form context and output
+#
+# <%= semantic_form_for(@post) do |f| %>
+# <%= f.actions do %>
+# <%= f.action :reset, :as => :input %>
+# <%= f.action :submit, :as => :input %>
+# <% end %>
+# <% end %>
+#
+# <form...>
+# <fieldset class="actions">
+# <ol>
+# <li class="action input_action" id="post_reset_action">
+# <input type="reset" value="Reset">
+# </li>
+# <li class="action input_action" id="post_submit_action">
+# <input type="submit" value="Create Post">
+# </li>
+# </ol>
+# </fieldset>
+# </form>
+#
+# @example Specifying a label with a String
+# <%= f.action :submit, :as => :input, :label => "Go" %>
+#
+# @example Pass HTML attributes down to the `<input>`
+# <%= f.action :submit, :as => :input, :button_html => { :class => 'pretty', :accesskey => 'g', :disable_with => "Wait..." } %>
+#
+# @example Access key can also be set as a top-level option
+# <%= f.action :submit, :as => :input, :accesskey => 'g' %>
+#
+# @example Pass HTML attributes down to the `<li>` wrapper (classes are appended to the existing classes)
+# <%= f.action :submit, :as => :input, :wrapper_html => { :class => 'special', :id => 'whatever' } %>
+# <%= f.action :submit, :as => :input, :wrapper_html => { :class => ['extra', 'special'], :id => 'whatever' } %>
+#
+# @option *args :label [String, Symbol]
+# Override the label text with a String or a symbol for an i18n translation key
+#
+# @option *args :button_html [Hash]
+# Override or add to the HTML attributes to be passed down to the `<input>` tag
+#
+# @option *args :wrapper_html [Hash]
+# Override or add to the HTML attributes to be passed down to the wrapping `<li>` tag
+#
+# @todo document i18n keys
+# @todo document i18n translation with :label (?)
+module Formtastic
+ module Actions
+ class InputAction
+ include Base
+ include Buttonish
+
+ def to_html
+ wrapper do
+ builder.submit(text, button_html)
+ end
+ end
+ end
+ end
+end
View
87 lib/formtastic/actions/link_action.rb
@@ -0,0 +1,87 @@
+# Outputs a link wrapped in the standard `<li>` wrapper. This the default for `:cancel` actions.
+# The link's URL defaults to Rails' built-in `:back` macro (the HTTP referrer, or Javascript for the
+# browser's history), but can be altered with the `:url` option.
+#
+# @example The `:as` can be ommitted, these are functionally equivalent
+# <%= f.action :cancel, :as => :link %>
+# <%= f.action :cancel %>
+#
+# @example Full form context and output
+#
+# <%= semantic_form_for(@post) do |f| %>
+# <%= f.actions do %>
+# <%= f.action :submit, :as => :input %>
+# <%= f.action :cancel, :as => :link %>
+# <% end %>
+# <% end %>
+#
+# <form...>
+# <fieldset class="actions">
+# <ol>
+# <li class="action input_action" id="post_submit_action">
+# <input type="submit" value="Create Post">
+# </li>
+# <li class="action link_action" id="post_cancel_action">
+# <a href="javascript:history.back()">Cancel</a>
+# </li>
+# </ol>
+# </fieldset>
+# </form>
+#
+# @example Modifying the URL for the link
+# <%= f.action :cancel, :as => :link, :url => "http://example.com/path" %>
+# <%= f.action :cancel, :as => :link, :url => "/path" %>
+# <%= f.action :cancel, :as => :link, :url => posts_path %>
+# <%= f.action :cancel, :as => :link, :url => url_for(...) %>
+# <%= f.action :cancel, :as => :link, :url => { :controller => "posts", :action => "index" } %>
+#
+# @example Specifying a label with a String
+# <%= f.action :cancel, :as => :link, :label => "Stop" %>
+#
+# @example Pass HTML attributes down to the `<a>`
+# <%= f.action :cancel, :as => :link, :button_html => { :class => 'pretty', :accesskey => 'x' } %>
+#
+# @example Access key can also be set as a top-level option
+# <%= f.action :cancel, :as => :link, :accesskey => 'x' %>
+#
+# @example Pass HTML attributes down to the `<li>` wrapper (classes are appended to the existing classes)
+# <%= f.action :cancel, :as => :link, :wrapper_html => { :class => 'special', :id => 'whatever' } %>
+# <%= f.action :cancel, :as => :link, :wrapper_html => { :class => ['extra', 'special'], :id => 'whatever' } %>
+#
+# @option *args :label [String, Symbol]
+# Override the label text with a String or a symbol for an i18n translation key
+#
+# @option *args :button_html [Hash]
+# Override or add to the HTML attributes to be passed down to the `<a>` tag
+#
+# @option *args :wrapper_html [Hash]
+# Override or add to the HTML attributes to be passed down to the wrapping `<li>` tag
+#
+# @todo document i18n keys
+# @todo document i18n translation with :label (?)
+# @todo :prefix and :suffix options? (can also be done with CSS or subclassing for custom Actions)
+module Formtastic
+ module Actions
+ class LinkAction
+
+ include Base
+
+ def supported_methods
+ [:cancel]
+ end
+
+ # TODO reset_action class?
+ def to_html
+ wrapper do
+ template.link_to(text, url, button_html)
+ end
+ end
+
+ def url
+ return options[:url] if options.key?(:url)
+ :back
+ end
+
+ end
+ end
+end
View
2  lib/formtastic/form_builder.rb
@@ -41,6 +41,8 @@ def self.configure(name, value = nil)
include Formtastic::Helpers::InputHelper
include Formtastic::Helpers::InputsHelper
include Formtastic::Helpers::ButtonsHelper
+ include Formtastic::Helpers::ActionHelper
+ include Formtastic::Helpers::ActionsHelper
include Formtastic::Helpers::ErrorsHelper
# This is a wrapper around Rails' `ActionView::Helpers::FormBuilder#fields_for`, originally
View
2  lib/formtastic/helpers.rb
@@ -2,6 +2,8 @@ module Formtastic
# @private
module Helpers
autoload :ButtonsHelper, 'formtastic/helpers/buttons_helper'
+ autoload :ActionHelper, 'formtastic/helpers/action_helper'
+ autoload :ActionsHelper, 'formtastic/helpers/actions_helper'
autoload :ErrorsHelper, 'formtastic/helpers/errors_helper'
autoload :FieldsetWrapper, 'formtastic/helpers/fieldset_wrapper'
autoload :FileColumnDetection, 'formtastic/helpers/file_column_detection'
View
109 lib/formtastic/helpers/action_helper.rb
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+module Formtastic
+ module Helpers
+ module ActionHelper
+
+ # Renders an action for the form (such as a subit/reset button, or a cancel link).
+ #
+ # Each action is wrapped in an `<li class="action">` tag with other classes added based on the
+ # type of action being rendered, and is intended to be rendered inside a {#buttons}
+ # block which wraps the button in a `fieldset` and `ol`.
+ #
+ # The textual value of the label can be changed from the default through the `:label`
+ # argument or through i18n.
+ #
+ # @example Basic usage
+ # # form
+ # <%= semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.actions do %>
+ # <%= f.action :submit %>
+ # <%= f.action :reset %>
+ # <%= f.action :cancel %>
+ # <% end %>
+ # <% end %>
+ #
+ # # output
+ # <form ...>
+ # ...
+ # <fieldset class="buttons">
+ # <ol>
+ # <li class="action input_action">
+ # <input name="commit" type="submit" value="Create Post">
+ # </li>
+ # <li class="action input_action">
+ # <input name="commit" type="reset" value="Reset Post">
+ # </li>
+ # <li class="action link_action">
+ # <a href="/posts">Cancel Post</a>
+ # </li>
+ # </ol>
+ # </fieldset>
+ # </form>
+ #
+ # @example Set the value through the `:label` option
+ # <%= f.action :submit, :label => "Go" %>
+ #
+ # @example Pass HTML attributes down to the tag inside the wrapper
+ # <%= f.action :submit, :button_html => { :class => 'pretty', :accesskey => 'g', :disable_with => "Wait..." } %>
+ #
+ # @example Pass HTML attributes down to the `<li>` wrapper
+ # <%= f.action :submit, :wrapper_html => { :class => 'special', :id => 'whatever' } %>
+ #
+ # @option *args :label [String, Symbol]
+ # Override the label text with a String or a symbold for an i18n translation key
+ #
+ # @option *args :button_html [Hash]
+ # Override or add to the HTML attributes to be passed down to the `<input>` tag
+ #
+ # @option *args :wrapper_html [Hash]
+ # Override or add to the HTML attributes to be passed down to the wrapping `<li>` tag
+ #
+ # @todo document i18n keys
+ def action(method, options = {})
+ options = options.dup # Allow options to be shared without being tainted by Formtastic
+ options[:as] ||= default_action_type(method, options)
+
+ klass = action_class(options[:as])
+
+ klass.new(self, template, @object, @object_name, method, options).to_html
+ end
+
+ protected
+
+ def default_action_type(method, options = {}) #:nodoc:
+ case method
+ when :submit then :input
+ when :reset then :input
+ when :cancel then :link
+ end
+ end
+
+ def action_class(as)
+ @input_classes_cache ||= {}
+ @input_classes_cache[as] ||= begin
+ begin
+ begin
+ custom_action_class_name(as).constantize
+ rescue NameError
+ standard_action_class_name(as).constantize
+ end
+ rescue NameError
+ raise Formtastic::UnknownActionError
+ end
+ end
+ end
+
+ # :as => :button # => ButtonAction
+ def custom_action_class_name(as)
+ "#{as.to_s.camelize}Action"
+ end
+
+ # :as => :button # => Formtastic::Actions::ButtonAction
+ def standard_action_class_name(as)
+ "Formtastic::Actions::#{as.to_s.camelize}Action"
+ end
+
+ end
+ end
+end
View
168 lib/formtastic/helpers/actions_helper.rb
@@ -0,0 +1,168 @@
+module Formtastic
+ module Helpers
+ # ActionsHelper encapsulates the responsibilties of the {#actions} DSL for acting on
+ # (submitting, cancelling, resetting) forms.
+ #
+ # {#actions} is a block helper used to wrap the form's actions (buttons, links) in a
+ # `<fieldset>` and `<ol>`, with each item in the list containing the markup representing a
+ # single action.
+ #
+ # <%= semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.actions do %>
+ # <%= f.action :submit
+ # <%= f.action :cancel
+ # <% end %>
+ # <% end %>
+ #
+ # The HTML output will be something like:
+ #
+ # <form class="formtastic" method="post" action="...">
+ # ...
+ # <fieldset class="actions">
+ # <ol>
+ # <li class="action input_action">
+ # <input type="submit" name="commit" value="Create Post">
+ # </li>
+ # <li class="action input_action">
+ # <a href="/posts">Cancel Post</a>
+ # </li>
+ # </ol>
+ # </fieldset>
+ # </form>
+ #
+ # It's important to note that the `semantic_form_for` and {#actions} blocks wrap the
+ # standard Rails `form_for` helper and form builder, so you have full access to every standard
+ # Rails form helper, with any HTML markup and ERB syntax, allowing you to "break free" from
+ # Formtastic when it doesn't suit to create your own buttons, links and actions:
+ #
+ # <%= semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.actions do %>
+ # <li class="save">
+ # <%= f.submit "Save" %>
+ # <li>
+ # <li class="cancel-link">
+ # Or <%= link_to "Cancel", posts_url %>
+ # <li>
+ # <% end %>
+ # <% end %>
+ #
+ # There are many other syntax variations and arguments to customize your form. See the
+ # full documentation of {#actions} and {#action} for details.
+ module ActionsHelper
+
+ include Formtastic::Helpers::FieldsetWrapper
+
+ # Creates a fieldset and ol tag wrapping for use around a set of buttons. It can be
+ # called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
+ # or with a list of named actions. These two examples are functionally equivalent:
+ #
+ # # With a block:
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <% f.actions do %>
+ # <%= f.action :submit %>
+ # <%= f.action :cancel %>
+ # <% end %>
+ # <% end %>
+ #
+ # # With a list of fields:
+ # <% semantic_form_for @post do |f| %>
+ # <%= f.actions :submit, :cancel %>
+ # <% end %>
+ #
+ # # Output:
+ # <form ...>
+ # <fieldset class="buttons">
+ # <ol>
+ # <li class="action input_action">
+ # <input type="submit" ...>
+ # </li>
+ # <li class="action link_action">
+ # <a href="...">...</a>
+ # </li>
+ # </ol>
+ # </fieldset>
+ # </form>
+ #
+ # All options except `:name` and `:title` are passed down to the fieldset as HTML
+ # attributes (`id`, `class`, `style`...). If provided, the `:name` or `:title` option is
+ # passed into a `<legend>` inside the `<fieldset>` to name the set of buttons.
+ #
+ # @example Quickly add button(s) to the form, accepting all default values, options and behaviors
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.actions %>
+ # <% end %>
+ #
+ # @example Specify which named buttons you want, accepting all default values, options and behaviors
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.actions :commit %>
+ # <% end %>
+ #
+ # @example Specify which named buttons you want, and name the fieldset
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.actions :commit, :name => "Actions" %>
+ # or
+ # <%= f.actions :commit, :label => "Actions" %>
+ # <% end %>
+ #
+ # @example Get full control over the action options
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.actions do %>
+ # <%= f.action :label => "Go", :button_html => { :class => "pretty" :disable_with => "Wait..." }, :wrapper_html => { ... }
+ # <% end %>
+ # <% end %>
+ #
+ # @example Make your own actions with standard Rails helpers or HTML
+ # <% semantic_form_for @post do |f| %>
+ # <%= f.actions do %>
+ # <li>
+ # ...
+ # </li>
+ # <% end %>
+ # <% end %>
+ #
+ # @example Add HTML attributes to the fieldset
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.actions :commit, :style => "border:1px;" %>
+ # or
+ # <%= f.actions :style => "border:1px;" do %>
+ # ...
+ # <% end %>
+ # <% end %>
+ #
+ # @option *args :label [String, Symbol]
+ # Optionally specify text for the legend of the fieldset
+ #
+ # @option *args :name [String, Symbol]
+ # Optionally specify text for the legend of the fieldset (alias for `:label`)
+ #
+ # @todo document i18n keys
+ def actions(*args, &block)
+ html_options = args.extract_options!
+ html_options[:class] ||= "actions"
+
+ if block_given?
+ field_set_and_list_wrapping(html_options, &block)
+ else
+ args = default_actions if args.empty?
+ contents = args.map { |action_name| action(action_name) }
+ field_set_and_list_wrapping(html_options, contents)
+ end
+ end
+
+ protected
+
+ def default_actions
+ [:submit]
+ end
+
+ end
+ end
+end
View
8 lib/formtastic/helpers/buttons_helper.rb
@@ -53,6 +53,8 @@ module Helpers
#
# There are many other syntax variations and arguments to customize your form. See the
# full documentation of {#buttons} and {#commit_button} for details.
+ #
+ # @deprecated ButtonsHelper will be removed after 2.1
module ButtonsHelper
include Formtastic::Helpers::FieldsetWrapper
include Formtastic::LocalizedString
@@ -164,7 +166,10 @@ module ButtonsHelper
# Optionally specify text for the legend of the fieldset (alias for `:label`)
#
# @todo document i18n keys
+ # @deprecated f.buttons is deprecated in favor of f.actions and will be removed after 2.1
def buttons(*args, &block)
+ ::ActiveSupport::Deprecation.warn("f.buttons is deprecated in favour of f.actions and will be removed from Formtastic after 2.1. Please see ActionsHelper and InputAction or ButtonAction for more information")
+
html_options = args.extract_options!
html_options[:class] ||= "buttons"
@@ -238,7 +243,10 @@ def buttons(*args, &block)
#
# @todo document i18n keys
# @todo strange that `:accesskey` seems to be supported in the top level args as well as `:button_html`
+ # @deprecated f.commit_button is deprecated in favor of f.actions and will be removed after 2.1
def commit_button(*args)
+ ::ActiveSupport::Deprecation.warn("f.commit_button is deprecated in favour of f.action(:submit) and will be removed from Formtastic after 2.1. Please see ActionsHelper and InputAction or ButtonAction for more information")
+
options = args.extract_options!
text = options.delete(:label) || args.shift
View
7 lib/formtastic/helpers/inputs_helper.rb
@@ -351,7 +351,10 @@ def association_columns(*by_associations) #:nodoc:
# Collects content columns (non-relation columns) for the current form object class.
def content_columns #:nodoc:
- model_name.constantize.content_columns.collect { |c| c.name.to_sym }.compact rescue []
+ # TODO: NameError is raised by Inflector.constantize. Consider checking if it exists instead.
+ begin klass = model_name.constantize; rescue NameError; return [] end
+ return [] unless klass.respond_to?(:content_columns)
+ klass.content_columns.collect { |c| c.name.to_sym }.compact
end
# Deals with :for option when it's supplied to inputs methods. Additional
@@ -397,4 +400,4 @@ def field_set_title_from_args(*args) #:nodoc:
end
end
-end
+end
View
11 lib/formtastic/inputs/select_input.rb
@@ -147,6 +147,7 @@ class SelectInput
def to_html
input_wrapping do
+ hidden_input <<
label_html <<
(options[:group_by] ? grouped_select_html : select_html)
end
@@ -172,6 +173,14 @@ def grouped_select_html
def include_blank
options.key?(:include_blank) ? options[:include_blank] : (single? && builder.include_blank_for_select_by_default)
end
+
+ def hidden_input
+ if multiple?
+ template.hidden_field_tag(input_html_options_name_multiple, '', :id => nil)
+ else
+ "".html_safe
+ end
+ end
def prompt?
!!options[:prompt]
@@ -188,7 +197,7 @@ def input_options
def input_html_options
extra_input_html_options.merge(super)
end
-
+
def extra_input_html_options
{
:multiple => multiple?,
View
2  lib/formtastic/version.rb
@@ -1,3 +1,3 @@
module Formtastic
- VERSION = "2.0.1"
+ VERSION = "2.1.0.beta1"
end
View
4 lib/generators/templates/_form.html.erb
@@ -5,7 +5,7 @@
<%- end -%>
<%% end %>
- <%%= f.buttons do %>
- <%%= f.commit_button %>
+ <%%= f.actions do %>
+ <%%= f.action :submit, :as => :input %>
<%% end %>
<%% end %>
View
4 lib/generators/templates/_form.html.haml
@@ -4,5 +4,5 @@
= f.input :<%= attribute.name %>
<%- end -%>
- = f.buttons do
- = f.commit_button
+ = f.actions do
+ = f.action :submit, :as => :input
View
2  lib/locale/en.yml
@@ -5,4 +5,6 @@ en:
:create: 'Create %{model}'
:update: 'Update %{model}'
:submit: 'Submit %{model}'
+ :cancel: 'Cancel %{model}'
+ :reset: 'Reset %{model}'
:required: 'required'
View
22 sample/basic_inputs.html
@@ -177,6 +177,28 @@
</li>
</ol>
</fieldset>
+
+ <fieldset class="actions">
+ <ol>
+ <li class="action input_action">
+ <input id="gem_submit" type="submit" value="Create Thing">
+ </li>
+ </ol>
+ </fieldset>
+
+ <fieldset class="actions">
+ <ol>
+ <li class="action button_action">
+ <button id="gem_submit" type="submit">Create Thing</button>
+ </li>
+ <li class="action button_action">
+ <button id="gem_submit" type="reset">Reset</button>
+ </li>
+ <li class="action link_action">
+ <a href="#">Cancel</button>
+ </li>
+ </ol>
+ </fieldset>
</form>
</body>
</html>
View
63 spec/actions/button_action_spec.rb
@@ -0,0 +1,63 @@
+# encoding: utf-8
+require 'spec_helper'
+
+describe 'ButtonAction', 'when submitting' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :button))
+ end)
+ end
+
+ it 'should render a submit type of button' do
+ output_buffer.should have_tag('li.action.button_action button[@type="submit"]')
+ end
+
+end
+
+describe 'ButtonAction', 'when resetting' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:reset, :as => :button))
+ end)
+ end
+
+ it 'should render a reset type of button' do
+ output_buffer.should have_tag('li.action.button_action button[@type="reset"]', :text => "Reset Post")
+ end
+
+ it 'should not render a value attribute' do
+ output_buffer.should_not have_tag('li.action.button_action button[@value]')
+ end
+
+end
+
+describe 'InputAction', 'when cancelling' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+ end
+
+ it 'should raise an error' do
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:cancel, :as => :button))
+ end)
+ }.should raise_error(Formtastic::UnsupportedMethodForAction)
+ end
+
+end
View
484 spec/actions/generic_action_spec.rb
@@ -0,0 +1,484 @@
+# encoding: utf-8
+require 'spec_helper'
+
+describe 'InputAction::Base' do
+
+ # Most basic Action class to test Base
+ class ::GenericAction
+ include ::Formtastic::Actions::Base
+
+ def supported_methods
+ [:submit, :reset, :cancel]
+ end
+
+ def to_html
+ wrapper do
+ builder.submit(text, button_html)
+ end
+ end
+ end
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+ end
+
+ describe 'wrapping HTML' do
+
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic,
+ :wrapper_html => { :foo => 'bah' }
+ ))
+ end)
+ end
+
+ it 'should add the #foo id to the li' do
+ output_buffer.should have_tag('li#post_submit_action')
+ end
+
+ it 'should add the .action and .generic_action classes to the li' do
+ output_buffer.should have_tag('li.action.generic_action')
+ end
+
+ it 'should pass :wrapper_html HTML attributes to the wrapper' do
+ output_buffer.should have_tag('li.action.generic_action[@foo="bah"]')
+ end
+
+ context "when a custom :id is provided" do
+
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic,
+ :wrapper_html => { :id => 'foo_bah_bing' }
+ ))
+ end)
+ end
+
+ it "should use the custom id" do
+ output_buffer.should have_tag('li#foo_bah_bing')
+ end
+
+ end
+
+ context "when a custom class is provided as a string" do
+
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic,
+ :wrapper_html => { :class => 'foo_bah_bing' }
+ ))
+ end)
+ end
+
+ it "should add the custom class strng to the existing classes" do
+ output_buffer.should have_tag('li.action.generic_action.foo_bah_bing')
+ end
+
+ end
+
+ context "when a custom class is provided as an array" do
+
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic,
+ :wrapper_html => { :class => ['foo_bah_bing', 'zing_boo'] }
+ ))
+ end)
+ end
+
+ it "should add the custom class strng to the existing classes" do
+ output_buffer.should have_tag('li.action.generic_action.foo_bah_bing.zing_boo')
+ end
+
+ end
+
+ end
+
+ describe 'button HTML' do
+
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic,
+ :button_html => { :foo => 'bah' }
+ ))
+ end)
+ end
+
+ it 'should pass :button_html HTML attributes to the button' do
+ output_buffer.should have_tag('li.action.generic_action input[@foo="bah"]')
+ end
+
+ it 'should respect a default_commit_button_accesskey configuration with nil' do
+ with_config :default_commit_button_accesskey, nil do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ end)
+ output_buffer.should_not have_tag('li.action input[@accesskey]')
+ end
+ end
+
+ it 'should respect a default_commit_button_accesskey configuration with a String' do
+ with_config :default_commit_button_accesskey, 's' do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ end)
+ output_buffer.should have_tag('li.action input[@accesskey="s"]')
+ end
+ end
+
+ it 'should respect an accesskey through options over configration' do
+ with_config :default_commit_button_accesskey, 's' do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic, :accesskey => 'o'))
+ end)
+ output_buffer.should_not have_tag('li.action input[@accesskey="s"]')
+ output_buffer.should have_tag('li.action input[@accesskey="o"]')
+ end
+ end
+
+ end
+
+ describe 'labelling' do
+
+ describe 'when used without object' do
+
+ describe 'when explicit label is provided' do
+ it 'should render an input with the explicitly specified label' do
+ concat(semantic_form_for(:post, :url => 'http://example.com') do |builder|
+ concat(builder.action(:submit, :as => :generic, :label => "Click!"))
+ concat(builder.action(:reset, :as => :generic, :label => "Reset!"))
+ concat(builder.action(:cancel, :as => :generic, :label => "Cancel!"))
+ end)
+ output_buffer.should have_tag('li.generic_action input[@value="Click!"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Reset!"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Cancel!"]')
+ end
+ end
+
+ describe 'when no explicit label is provided' do
+ describe 'when no I18n-localized label is provided' do
+ before do
+ ::I18n.backend.store_translations :en, :formtastic => {
+ :submit => 'Submit %{model}',
+ :reset => 'Reset %{model}',
+ :cancel => 'Cancel %{model}'
+ }
+ end
+
+ after do
+ ::I18n.backend.reload!
+ end
+
+ it 'should render an input with default I18n-localized label (fallback)' do
+ concat(semantic_form_for(:post, :url => 'http://example.com') do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ concat(builder.action(:reset, :as => :generic))
+ concat(builder.action(:cancel, :as => :generic))
+ end)
+ output_buffer.should have_tag('li.generic_action input[@value="Submit Post"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Cancel Post"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Reset Post"]')
+ end
+ end
+
+ describe 'when I18n-localized label is provided' do
+
+ before do
+ ::I18n.backend.store_translations :en,
+ :formtastic => {
+ :actions => {
+ :submit => 'Custom Submit',
+ :reset => 'Custom Reset',
+ :cancel => 'Custom Cancel'
+ }
+ }
+ end
+
+ after do
+ ::I18n.backend.reload!
+ end
+
+ it 'should render an input with localized label (I18n)' do
+ with_config :i18n_lookups_by_default, true do
+ ::I18n.backend.store_translations :en,
+ :formtastic => {
+ :actions => {
+ :post => {
+ :submit => 'Custom Submit %{model}',
+ :reset => 'Custom Reset %{model}',
+ :cancel => 'Custom Cancel %{model}'
+ }
+ }
+ }
+ concat(semantic_form_for(:post, :url => 'http://example.com') do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ concat(builder.action(:reset, :as => :generic))
+ concat(builder.action(:cancel, :as => :generic))
+ end)
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Submit Post"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Reset Post"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Cancel Post"]})
+ end
+ end
+
+ it 'should render an input with anoptional localized label (I18n) - if first is not set' do
+ with_config :i18n_lookups_by_default, true do
+ concat(semantic_form_for(:post, :url => 'http://example.com') do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ concat(builder.action(:reset, :as => :generic))
+ concat(builder.action(:cancel, :as => :generic))
+ end)
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Submit"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Reset"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Cancel"]})
+ end
+ end
+
+ end
+ end
+ end
+
+ describe 'when used on a new record' do
+ before do
+ @new_post.stub!(:new_record?).and_return(true)
+ end
+
+ describe 'when explicit label is provided' do
+ it 'should render an input with the explicitly specified label' do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic, :label => "Click!"))
+ concat(builder.action(:reset, :as => :generic, :label => "Reset!"))
+ concat(builder.action(:cancel, :as => :generic, :label => "Cancel!"))
+ end)
+ output_buffer.should have_tag('li.generic_action input[@value="Click!"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Reset!"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Cancel!"]')
+ end
+ end
+
+ describe 'when no explicit label is provided' do
+ describe 'when no I18n-localized label is provided' do
+ before do
+ ::I18n.backend.store_translations :en, :formtastic => {
+ :create => 'Create %{model}',
+ :reset => 'Reset %{model}',
+ :cancel => 'Cancel %{model}'
+ }
+ end
+
+ after do
+ ::I18n.backend.reload!
+ end
+
+ it 'should render an input with default I18n-localized label (fallback)' do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ concat(builder.action(:reset, :as => :generic))
+ concat(builder.action(:cancel, :as => :generic))
+ end)
+ output_buffer.should have_tag('li.generic_action input[@value="Create Post"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Reset Post"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Cancel Post"]')
+ end
+ end
+
+ describe 'when I18n-localized label is provided' do
+ before do
+ ::I18n.backend.store_translations :en,
+ :formtastic => {
+ :actions => {
+ :create => 'Custom Create',
+ :reset => 'Custom Reset',
+ :cancel => 'Custom Cancel'
+ }
+ }
+ end
+
+ after do
+ ::I18n.backend.reload!
+ end
+
+ it 'should render an input with localized label (I18n)' do
+ with_config :i18n_lookups_by_default, true do
+ ::I18n.backend.store_translations :en,
+ :formtastic => {
+ :actions => {
+ :post => {
+ :create => 'Custom Create %{model}',
+ :reset => 'Custom Reset %{model}',
+ :cancel => 'Custom Cancel %{model}'
+ }
+ }
+ }
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ concat(builder.action(:reset, :as => :generic))
+ concat(builder.action(:cancel, :as => :generic))
+ end)
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Create Post"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Reset Post"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Cancel Post"]})
+ end
+ end
+
+ it 'should render an input with anoptional localized label (I18n) - if first is not set' do
+ with_config :i18n_lookups_by_default, true do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ concat(builder.action(:reset, :as => :generic))
+ concat(builder.action(:cancel, :as => :generic))
+ end)
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Create"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Reset"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Cancel"]})
+ end
+ end
+
+ end
+ end
+ end
+
+ describe 'when used on an existing record' do
+ before do
+ @new_post.stub!(:persisted?).and_return(true)
+ end
+
+ describe 'when explicit label is provided' do
+ it 'should render an input with the explicitly specified label' do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic, :label => "Click!"))
+ concat(builder.action(:reset, :as => :generic, :label => "Reset!"))
+ concat(builder.action(:cancel, :as => :generic, :label => "Cancel!"))
+ end)
+ output_buffer.should have_tag('li.generic_action input[@value="Click!"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Reset!"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Cancel!"]')
+ end
+ end
+
+ describe 'when no explicit label is provided' do
+ describe 'when no I18n-localized label is provided' do
+ before do
+ ::I18n.backend.store_translations :en, :formtastic => {
+ :update => 'Save %{model}',
+ :reset => 'Reset %{model}',
+ :cancel => 'Cancel %{model}'
+ }
+ end
+
+ after do
+ ::I18n.backend.reload!
+ end
+
+ it 'should render an input with default I18n-localized label (fallback)' do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ concat(builder.action(:reset, :as => :generic))
+ concat(builder.action(:cancel, :as => :generic))
+ end)
+ output_buffer.should have_tag('li.generic_action input[@value="Save Post"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Reset Post"]')
+ output_buffer.should have_tag('li.generic_action input[@value="Cancel Post"]')
+ end
+ end
+
+ describe 'when I18n-localized label is provided' do
+ before do
+ ::I18n.backend.reload!
+ ::I18n.backend.store_translations :en,
+ :formtastic => {
+ :actions => {
+ :update => 'Custom Save',
+ :reset => 'Custom Reset',
+ :cancel => 'Custom Cancel'
+ }
+ }
+ end
+
+ after do
+ ::I18n.backend.reload!
+ end
+
+ it 'should render an input with localized label (I18n)' do
+ with_config :i18n_lookups_by_default, true do
+ ::I18n.backend.store_translations :en,
+ :formtastic => {
+ :actions => {
+ :post => {
+ :update => 'Custom Save %{model}',
+ :reset => 'Custom Reset %{model}',
+ :cancel => 'Custom Cancel %{model}'
+ }
+ }
+ }
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ concat(builder.action(:reset, :as => :generic))
+ concat(builder.action(:cancel, :as => :generic))
+ end)
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Save Post"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Reset Post"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Cancel Post"]})
+ end
+ end
+
+ it 'should render an input with anoptional localized label (I18n) - if first is not set' do
+ with_config :i18n_lookups_by_default, true do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ concat(builder.action(:reset, :as => :generic))
+ concat(builder.action(:cancel, :as => :generic))
+ end)
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Save"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Reset"]})
+ output_buffer.should have_tag(%Q{li.generic_action input[@value="Custom Cancel"]})
+ ::I18n.backend.store_translations :en, :formtastic => {}
+ end
+ end
+
+ end
+ end
+ end
+ end
+
+ describe 'when the model is two words' do
+
+ before do
+ output_buffer = ''
+ class ::UserPost
+ extend ActiveModel::Naming if defined?(ActiveModel::Naming)
+ include ActiveModel::Conversion if defined?(ActiveModel::Conversion)
+
+ def id
+ end
+
+ def persisted?
+ end
+
+ # Rails does crappy human_name
+ def self.human_name
+ "User post"
+ end
+ end
+ @new_user_post = ::UserPost.new
+
+ @new_user_post.stub!(:new_record?).and_return(true)
+ concat(semantic_form_for(@new_user_post, :url => '') do |builder|
+ concat(builder.action(:submit, :as => :generic))
+ concat(builder.action(:reset, :as => :generic))
+ concat(builder.action(:cancel, :as => :generic))
+ end)
+ end
+
+ it "should render the string as the value of the button" do
+ output_buffer.should have_tag('li input[@value="Create User post"]')
+ output_buffer.should have_tag('li input[@value="Reset User post"]')
+ output_buffer.should have_tag('li input[@value="Cancel User post"]')
+ end
+
+ end
+
+end
View
59 spec/actions/input_action_spec.rb
@@ -0,0 +1,59 @@
+# encoding: utf-8
+require 'spec_helper'
+
+describe 'InputAction', 'when submitting' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :input))
+ end)
+ end
+
+ it 'should render a submit type of input' do
+ output_buffer.should have_tag('li.action.input_action input[@type="submit"]')
+ end
+
+end
+
+describe 'InputAction', 'when resetting' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:reset, :as => :input))
+ end)
+ end
+
+ it 'should render a reset type of input' do
+ output_buffer.should have_tag('li.action.input_action input[@type="reset"]')
+ end
+
+end
+
+describe 'InputAction', 'when cancelling' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+ end
+
+ it 'should raise an error' do
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:cancel, :as => :input))
+ end)
+ }.should raise_error(Formtastic::UnsupportedMethodForAction)
+ end
+
+end
View
92 spec/actions/link_action_spec.rb
@@ -0,0 +1,92 @@
+# encoding: utf-8
+require 'spec_helper'
+
+describe 'LinkAction', 'when cancelling' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+ end
+
+ context 'without a :url' do
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:cancel, :as => :link))
+ end)
+ end
+
+ it 'should render a submit type of input' do
+ output_buffer.should have_tag('li.action.link_action a[@href="javascript:history.back()"]')
+ end
+
+ end
+
+ context 'with a :url as String' do
+
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:cancel, :as => :link, :url => "http://foo.bah/baz"))
+ end)
+ end
+
+ it 'should render a submit type of input' do
+ output_buffer.should have_tag('li.action.link_action a[@href="http://foo.bah/baz"]')
+ end
+
+ end
+
+ context 'with a :url as Hash' do
+
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:cancel, :as => :link, :url => { :action => "foo" }))
+ end)
+ end
+
+ it 'should render a submit type of input' do
+ output_buffer.should have_tag('li.action.link_action a[@href="/mock/path"]')
+ end
+
+ end
+
+end
+
+describe 'LinkAction', 'when submitting' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+ end
+
+ it 'should raise an error' do
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :link))
+ end)
+ }.should raise_error(Formtastic::UnsupportedMethodForAction)
+ end
+
+end
+
+describe 'LinkAction', 'when submitting' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+ end
+
+ it 'should raise an error' do
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:reset, :as => :link))
+ end)
+ }.should raise_error(Formtastic::UnsupportedMethodForAction)
+ end
+
+end
View
365 spec/helpers/action_helper_spec.rb
@@ -0,0 +1,365 @@
+# encoding: utf-8
+require 'spec_helper'
+
+describe 'Formtastic::FormBuilder#action' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+ end
+
+ after do
+ ::I18n.backend.reload!
+ end
+
+ describe 'arguments and options' do
+
+ it 'should require the first argument (the action method)' do
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action()) # no args passed in at all
+ end)
+ }.should raise_error(ArgumentError)
+ end
+
+ describe ':as option' do
+
+ describe 'when not provided' do
+
+ it 'should default to a commit for commit' do
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
+ concat(builder.action(:submit))
+ end)
+ output_buffer.should have_tag('form li.action.input_action', :count => 1)
+ end
+
+ it 'should default to a button for reset' do
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
+ concat(builder.action(:reset))
+ end)
+ output_buffer.should have_tag('form li.action.input_action', :count => 1)
+ end
+
+ it 'should default to a link for cancel' do
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
+ concat(builder.action(:cancel))
+ end)
+ output_buffer.should have_tag('form li.action.link_action', :count => 1)
+ end
+ end
+
+ it 'should call the corresponding action class with .to_html' do
+ [:input, :button, :link].each do |action_style|
+ semantic_form_for(:project, :url => "http://test.host") do |builder|
+ action_instance = mock('Action instance')
+ action_class = "#{action_style.to_s}_action".classify
+ action_constant = "Formtastic::Actions::#{action_class}".constantize
+
+ action_constant.should_receive(:new).and_return(action_instance)
+ action_instance.should_receive(:to_html).and_return("some HTML")
+
+ concat(builder.action(:submit, :as => action_style))
+ end
+ end
+ end
+
+ end
+
+ #describe ':label option' do
+ #
+ # describe 'when provided' do
+ # it 'should be passed down to the label tag' do
+ # concat(semantic_form_for(@new_post) do |builder|
+ # concat(builder.input(:title, :label => "Kustom"))
+ # end)
+ # output_buffer.should have_tag("form li label", /Kustom/)
+ # end
+ #
+ # it 'should not generate a label if false' do
+ # concat(semantic_form_for(@new_post) do |builder|
+ # concat(builder.input(:title, :label => false))
+ # end)
+ # output_buffer.should_not have_tag("form li label")
+ # end
+ #
+ # it 'should be dupped if frozen' do
+ # concat(semantic_form_for(@new_post) do |builder|
+ # concat(builder.input(:title, :label => "Kustom".freeze))
+ # end)
+ # output_buffer.should have_tag("form li label", /Kustom/)
+ # end
+ # end
+ #
+ # describe 'when not provided' do
+ # describe 'when localized label is provided' do
+ # describe 'and object is given' do
+ # describe 'and label_str_method not :humanize' do
+ # it 'should render a label with localized text and not apply the label_str_method' do
+ # with_config :label_str_method, :reverse do
+ # @localized_label_text = 'Localized title'
+ # @new_post.stub!(:meta_description)
+ # ::I18n.backend.store_translations :en,
+ # :formtastic => {
+ # :labels => {
+ # :meta_description => @localized_label_text
+ # }
+ # }
+ #
+ # concat(semantic_form_for(@new_post) do |builder|
+ # concat(builder.input(:meta_description))
+ # end)
+ # output_buffer.should have_tag('form li label', /Localized title/)
+ # end
+ # end
+ # end
+ # end
+ # end
+ #
+ # describe 'when localized label is NOT provided' do
+ # describe 'and object is not given' do
+ # it 'should default the humanized method name, passing it down to the label tag' do
+ # ::I18n.backend.store_translations :en, :formtastic => {}
+ # with_config :label_str_method, :humanize do
+ # concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
+ # concat(builder.input(:meta_description))
+ # end)
+ # output_buffer.should have_tag("form li label", /#{'meta_description'.humanize}/)
+ # end
+ # end
+ # end
+ #
+ # describe 'and object is given' do
+ # it 'should delegate the label logic to class human attribute name and pass it down to the label tag' do
+ # @new_post.stub!(:meta_description) # a two word method name
+ # @new_post.class.should_receive(:human_attribute_name).with('meta_description').and_return('meta_description'.humanize)
+ #
+ # concat(semantic_form_for(@new_post) do |builder|
+ # concat(builder.input(:meta_description))
+ # end)
+ # output_buffer.should have_tag("form li label", /#{'meta_description'.humanize}/)
+ # end
+ # end
+ #
+ # describe 'and object is given with label_str_method set to :capitalize' do
+ # it 'should capitalize method name, passing it down to the label tag' do
+ # with_config :label_str_method, :capitalize do
+ # @new_post.stub!(:meta_description)
+ #
+ # concat(semantic_form_for(@new_post) do |builder|
+ # concat(builder.input(:meta_description))
+ # end)
+ # output_buffer.should have_tag("form li label", /#{'meta_description'.capitalize}/)
+ # end
+ # end
+ # end
+ # end
+ #
+ # describe 'when localized label is provided' do
+ # before do
+ # @localized_label_text = 'Localized title'
+ # @default_localized_label_text = 'Default localized title'
+ # ::I18n.backend.store_translations :en,
+ # :formtastic => {
+ # :labels => {
+ # :title => @default_localized_label_text,
+ # :published => @default_localized_label_text,
+ # :post => {
+ # :title => @localized_label_text,
+ # :published => @default_localized_label_text
+ # }
+ # }
+ # }
+ # end
+ #
+ # it 'should render a label with localized label (I18n)' do
+ # with_config :i18n_lookups_by_default, false do
+ # concat(semantic_form_for(@new_post) do |builder|
+ # concat(builder.input(:title, :label => true))
+ # concat(builder.input(:published, :as => :boolean, :label => true))
+ # end)
+ # output_buffer.should have_tag('form li label', Regexp.new('^' + @localized_label_text))
+ # end
+ # end
+ #
+ # it 'should render a hint paragraph containing an optional localized label (I18n) if first is not set' do
+ # with_config :i18n_lookups_by_default, false do
+ # ::I18n.backend.store_translations :en,
+ # :formtastic => {
+ # :labels => {
+ # :post => {
+ # :title => nil,
+ # :published => nil
+ # }
+ # }
+ # }
+ # concat(semantic_form_for(@new_post) do |builder|
+ # concat(builder.input(:title, :label => true))
+ # concat(builder.input(:published, :as => :boolean, :label => true))
+ # end)
+ # output_buffer.should have_tag('form li label', Regexp.new('^' + @default_localized_label_text))
+ # end
+ # end
+ # end
+ # end
+ #
+ #end
+ #
+ describe ':wrapper_html option' do
+
+ describe 'when provided' do
+ it 'should be passed down to the li tag' do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :wrapper_html => {:id => :another_id}))
+ end)
+ output_buffer.should have_tag("form li#another_id")
+ end
+
+ it 'should append given classes to li default classes' do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :wrapper_html => {:class => :another_class}))
+ end)
+ output_buffer.should have_tag("form li.action")
+ output_buffer.should have_tag("form li.input_action")
+ output_buffer.should have_tag("form li.another_class")
+ end
+
+ it 'should allow classes to be an array' do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :wrapper_html => {:class => [ :my_class, :another_class ]}))
+ end)
+ output_buffer.should have_tag("form li.action")
+ output_buffer.should have_tag("form li.input_action")
+ output_buffer.should have_tag("form li.my_class")
+ output_buffer.should have_tag("form li.another_class")
+ end
+ end
+
+ describe 'when not provided' do
+ it 'should use default id and class' do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit))
+ end)
+ output_buffer.should have_tag("form li#post_submit_action")
+ output_buffer.should have_tag("form li.action")
+ output_buffer.should have_tag("form li.input_action")
+ end
+ end
+
+ end
+
+ end
+
+ describe 'instantiating an action class' do
+
+ context 'when a class does not exist' do
+ it "should raise an error" do
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.action(:submit, :as => :non_existant)
+ end)
+ }.should raise_error(Formtastic::UnknownActionError)
+ end
+ end
+
+ context 'when a customized top-level class does not exist' do
+
+ it 'should instantiate the Formtastic action' do
+ action = mock('action', :to_html => 'some HTML')
+ Formtastic::Actions::ButtonAction.should_receive(:new).and_return(action)
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.action(:commit, :as => :button)
+ end)
+ end
+
+ end
+
+ describe 'when a top-level (custom) action class exists' do
+ it "should instantiate the top-level action instead of the Formtastic one" do
+ class ::ButtonAction < Formtastic::Actions::ButtonAction
+ end
+
+ action = mock('action', :to_html => 'some HTML')
+ Formtastic::Actions::ButtonAction.should_not_receive(:new).and_return(action)
+ ::ButtonAction.should_receive(:new).and_return(action)
+
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.action(:commit, :as => :button)
+ end)
+ end
+ end
+
+ describe 'when instantiated multiple times with the same action type' do
+
+ it "should be cached (not calling the internal methods)" do
+ # TODO this is really tied to the underlying implementation
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.should_receive(:custom_action_class_name).with(:button).once.and_return(::Formtastic::Actions::ButtonAction)
+ builder.action(:submit, :as => :button)
+ builder.action(:submit, :as => :button)
+ end)
+ end
+
+ end
+
+ describe 'support for :as on each action' do
+
+ it "should raise an error when the action does not support the :as" do
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :link))
+ end)
+ }.should raise_error(Formtastic::UnsupportedMethodForAction)
+
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:cancel, :as => :input))
+ end)
+ }.should raise_error(Formtastic::UnsupportedMethodForAction)
+
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:cancel, :as => :button))
+ end)
+ }.should raise_error(Formtastic::UnsupportedMethodForAction)
+ end
+
+ it "should not raise an error when the action does not support the :as" do
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:cancel, :as => :link))
+ end)
+ }.should_not raise_error(Formtastic::UnsupportedMethodForAction)
+
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :input))
+ end)
+ }.should_not raise_error(Formtastic::UnsupportedMethodForAction)
+
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:submit, :as => :button))
+ end)
+ }.should_not raise_error(Formtastic::UnsupportedMethodForAction)
+
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:reset, :as => :input))
+ end)
+ }.should_not raise_error(Formtastic::UnsupportedMethodForAction)
+
+ lambda {
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.action(:reset, :as => :button))
+ end)
+ }.should_not raise_error(Formtastic::UnsupportedMethodForAction)
+ end
+
+ end
+
+ end
+
+end
+
View
143 spec/helpers/actions_helper_spec.rb
@@ -0,0 +1,143 @@
+# encoding: utf-8