diff --git a/lib/formtastic-bootstrap/form_builder.rb b/lib/formtastic-bootstrap/form_builder.rb
index 8a524e0e..c3de423a 100644
--- a/lib/formtastic-bootstrap/form_builder.rb
+++ b/lib/formtastic-bootstrap/form_builder.rb
@@ -31,6 +31,7 @@ def self.default_hint_class=(hint_class)
include FormtasticBootstrap::Helpers::InputHelper # Revisit
include FormtasticBootstrap::Helpers::InputsHelper
+ include FormtasticBootstrap::Helpers::ErrorsHelper
include FormtasticBootstrap::Helpers::ActionHelper
include FormtasticBootstrap::Helpers::ActionsHelper
# include Formtastic::Helpers::ErrorsHelper
diff --git a/lib/formtastic-bootstrap/helpers/errors_helper.rb b/lib/formtastic-bootstrap/helpers/errors_helper.rb
new file mode 100644
index 00000000..4c956f4c
--- /dev/null
+++ b/lib/formtastic-bootstrap/helpers/errors_helper.rb
@@ -0,0 +1,70 @@
+module FormtasticBootstrap
+ module Helpers
+ module ErrorsHelper
+ include Formtastic::Helpers::FileColumnDetection
+ include Formtastic::Helpers::Reflection
+ include Formtastic::LocalizedString
+
+ INLINE_ERROR_TYPES = [:sentence, :list, :first]
+
+ # Generates a bootstrap error alert element containing
+ # an unordered list of error messages on the base object and optionally for a given
+ # set of named attribute. This is idea for rendering a block of error messages at the top of
+ # the form for hidden/special/virtual attributes (the Paperclip Rails plugin does this), or
+ # errors on the base model.
+ #
+ # A hash can be used as the last set of arguments to pass HTML attributes to the `
`
+ # wrapper.
+ #
+ # @example A list of errors on the base model
+ # <%= semantic_form_for ... %>
+ # <%= f.semantic_errors %>
+ # ...
+ # <% end %>
+ #
+ # @example A list of errors on the base and named attributes
+ # <%= semantic_form_for ... %>
+ # <%= f.semantic_errors :something_special %>
+ # ...
+ # <% end %>
+ #
+ # @example A list of errors on the base model, with custom HTML attributes
+ # <%= semantic_form_for ... %>
+ # <%= f.semantic_errors :class => "awesome" %>
+ # ...
+ # <% end %>
+ #
+ # @example A list of errors on the base model and named attributes, with custom HTML attributes
+ # <%= semantic_form_for ... %>
+ # <%= f.semantic_errors :something_special, :something_else, :class => "awesome", :onclick => "Awesome();" %>
+ # ...
+ # <% end %>
+ def semantic_errors(*args)
+ html_options = args.extract_options!
+ args = args - [:base]
+ full_errors = args.inject([]) do |array, method|
+ attribute = localized_string(method, method.to_sym, :label) || humanized_attribute_name(method)
+ errors = Array(@object.errors[method.to_sym]).to_sentence
+ errors.present? ? array << [attribute, errors].join(" ") : array ||= []
+ end
+ full_errors << @object.errors[:base]
+ full_errors.flatten!
+ full_errors.compact!
+ return nil if full_errors.blank?
+
+ if html_options[:class].blank?
+ html_options[:class] = "alert alert-error"
+ else
+ html_options[:class] = "alert alert-error " + html_options[:class]
+ end
+
+ template.content_tag(:div, html_options) do
+ template.content_tag(:button, "×".html_safe, :class => "close", "data-dismiss" => "alert") +
+ template.content_tag(:ul, {class: "error-list"}) do
+ Formtastic::Util.html_safe(full_errors.map { |error| template.content_tag(:li, Formtastic::Util.html_safe(error)) }.join)
+ end
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/helpers/semantic_errors_helper_spec.rb b/spec/helpers/semantic_errors_helper_spec.rb
new file mode 100644
index 00000000..42fa4dda
--- /dev/null
+++ b/spec/helpers/semantic_errors_helper_spec.rb
@@ -0,0 +1,112 @@
+# encoding: utf-8
+require 'spec_helper'
+
+describe 'FormtasticBootstrap::FormBuilder#semantic_errors' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+ @title_errors = ['must not be blank', 'must be awesome']
+ @base_errors = ['base error message', 'nasty error']
+ @base_error = 'one base error'
+ @errors = mock('errors')
+ @new_post.stub!(:errors).and_return(@errors)
+ end
+
+ describe 'when there is only one error on base' do
+ before do
+ @errors.stub!(:[]).with(errors_matcher(:base)).and_return(@base_error)
+ end
+
+ it 'should render an alert with an unordered list' do
+ semantic_form_for(@new_post) do |builder|
+ builder.semantic_errors.should have_tag('.alert.alert-error ul.error-list li', @base_error)
+ end
+ end
+ end
+
+ describe 'when there is more than one error on base' do
+ before do
+ @errors.stub!(:[]).with(errors_matcher(:base)).and_return(@base_errors)
+ end
+
+ it 'should render an unordered list' do
+ semantic_form_for(@new_post) do |builder|
+ builder.semantic_errors.should have_tag('.alert.alert-error ul.error-list')
+ @base_errors.each do |error|
+ builder.semantic_errors.should have_tag('.alert.alert-error ul.error-list li', error)
+ end
+ end
+ end
+ end
+
+ describe 'when there are errors on title' do
+ before do
+ @errors.stub!(:[]).with(errors_matcher(:title)).and_return(@title_errors)
+ @errors.stub!(:[]).with(errors_matcher(:base)).and_return([])
+ end
+
+ it 'should render an unordered list' do
+ semantic_form_for(@new_post) do |builder|
+ title_name = builder.send(:localized_string, :title, :title, :label) || builder.send(:humanized_attribute_name, :title)
+ builder.semantic_errors(:title).should have_tag('.alert.alert-error ul.error-list li', title_name << " " << @title_errors.to_sentence)
+ end
+ end
+ end
+
+ describe 'when there are errors on title and base' do
+ before do
+ @errors.stub!(:[]).with(errors_matcher(:title)).and_return(@title_errors)
+ @errors.stub!(:[]).with(errors_matcher(:base)).and_return(@base_error)
+ end
+
+ it 'should render an unordered list' do
+ semantic_form_for(@new_post) do |builder|
+ title_name = builder.send(:localized_string, :title, :title, :label) || builder.send(:humanized_attribute_name, :title)
+ builder.semantic_errors(:title).should have_tag('.alert.alert-error ul.error-list li', title_name << " " << @title_errors.to_sentence)
+ builder.semantic_errors(:title).should have_tag('.alert.alert-error ul.error-list li', @base_error)
+ end
+ end
+ end
+
+ describe 'when there are no errors' do
+ before do
+ @errors.stub!(:[]).with(errors_matcher(:title)).and_return(nil)
+ @errors.stub!(:[]).with(errors_matcher(:base)).and_return(nil)
+ end
+
+ it 'should return nil' do
+ semantic_form_for(@new_post) do |builder|
+ builder.semantic_errors(:title).should be_nil
+ end
+ end
+ end
+
+ describe 'when there is one error on base and options with class is passed' do
+ before do
+ @errors.stub!(:[]).with(errors_matcher(:base)).and_return(@base_error)
+ end
+
+ it 'should render an unordered list with given class' do
+ semantic_form_for(@new_post) do |builder|
+ builder.semantic_errors(:class => "awesome").should have_tag('.alert.alert-error.awesome ul.error-list li', @base_error)
+ end
+ end
+ end
+
+ describe 'when :base is passed in as an argument' do
+ before do
+ @errors.stub!(:[]).with(errors_matcher(:base)).and_return(@base_error)
+ end
+
+ it 'should ignore :base and only render base errors once' do
+ semantic_form_for(@new_post) do |builder|
+ builder.semantic_errors(:base).should have_tag('ul li', :count => 1)
+ builder.semantic_errors(:base).should_not have_tag('ul li', "Base #{@base_error}")
+ end
+ end
+ end
+
+end
diff --git a/vendor/assets/stylesheets/formtastic-bootstrap.css b/vendor/assets/stylesheets/formtastic-bootstrap.css
index 35998a37..7268c44a 100644
--- a/vendor/assets/stylesheets/formtastic-bootstrap.css
+++ b/vendor/assets/stylesheets/formtastic-bootstrap.css
@@ -1,3 +1,7 @@
.hidden {
display: none;
}
+
+.alert.alert-error .error-list {
+ margin: 0px;
+}