diff --git a/README.md b/README.md
index 84da7087..eeb28637 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,6 @@ Tried to mimic its code structure as much as possible.
## To Do
* Field Types
* Basic Formtastic
- * :check_boxes
* :time_zone
* :date
* :datetime
diff --git a/lib/formtastic-bootstrap/inputs.rb b/lib/formtastic-bootstrap/inputs.rb
index 44e511b0..a7053fbe 100644
--- a/lib/formtastic-bootstrap/inputs.rb
+++ b/lib/formtastic-bootstrap/inputs.rb
@@ -1,5 +1,6 @@
require "formtastic-bootstrap/inputs/base"
require "formtastic-bootstrap/inputs/boolean_input"
+require "formtastic-bootstrap/inputs/check_boxes_input"
require "formtastic-bootstrap/inputs/email_input"
require "formtastic-bootstrap/inputs/hidden_input"
require "formtastic-bootstrap/inputs/number_input"
diff --git a/lib/formtastic-bootstrap/inputs/base/choices.rb b/lib/formtastic-bootstrap/inputs/base/choices.rb
index 49674e3d..68a6d685 100644
--- a/lib/formtastic-bootstrap/inputs/base/choices.rb
+++ b/lib/formtastic-bootstrap/inputs/base/choices.rb
@@ -14,7 +14,7 @@ def choices_wrapping(&block)
end
def choices_wrapping_html_options
- # Call the Formtastic one explicity and append?
+ # TODO Call the Formtastic one explicity and append?
{ :class => "choices input" }
end
@@ -29,14 +29,6 @@ def choices_group_wrapping_html_options
{ :class => "choices-group inputs-list" }
end
- def choice_html(choice)
- template.content_tag(:label, label_html_options.merge(:for => choice_input_dom_id(choice), :class => nil)) do
- builder.radio_button(input_name, choice_value(choice), input_html_options.merge(choice_html_options(choice)).merge(:required => false)) <<
-
- choice_label(choice)
- end
- end
-
def choice_label(choice)
"\n".html_safe + template.content_tag(:span) do
# (choice.is_a?(Array) ? choice.first : choice).to_s
diff --git a/lib/formtastic-bootstrap/inputs/boolean_input.rb b/lib/formtastic-bootstrap/inputs/boolean_input.rb
index de4365d2..8b8f0f9a 100644
--- a/lib/formtastic-bootstrap/inputs/boolean_input.rb
+++ b/lib/formtastic-bootstrap/inputs/boolean_input.rb
@@ -1,3 +1,4 @@
+# TODO See if this can be refactored to make use of some of the Choices code.
module FormtasticBootstrap
module Inputs
class BooleanInput < Formtastic::Inputs::BooleanInput
diff --git a/lib/formtastic-bootstrap/inputs/check_boxes_input.rb b/lib/formtastic-bootstrap/inputs/check_boxes_input.rb
new file mode 100644
index 00000000..254c265e
--- /dev/null
+++ b/lib/formtastic-bootstrap/inputs/check_boxes_input.rb
@@ -0,0 +1,35 @@
+module FormtasticBootstrap
+ module Inputs
+ class CheckBoxesInput < Formtastic::Inputs::CheckBoxesInput
+ include Base
+ include Base::Choices
+
+ def to_html
+ input_wrapping do
+ legend_html <<
+ hidden_field_for_all <<
+ choices_wrapping do
+ choices_group_wrapping do
+ collection.map { |choice|
+ choice_wrapping(choice_wrapping_html_options(choice)) do
+ choice_html(choice)
+ end
+ }.join("\n").html_safe
+ end
+ end
+ end
+ end
+
+ def choice_html(choice)
+ template.content_tag(:label,
+ hidden_fields? ?
+ check_box_with_hidden_input(choice) :
+ check_box_without_hidden_input(choice) <<
+ choice_label(choice),
+ label_html_options.merge(:for => choice_input_dom_id(choice), :class => nil)
+ )
+ end
+
+ end
+ end
+end
diff --git a/lib/formtastic-bootstrap/inputs/radio_input.rb b/lib/formtastic-bootstrap/inputs/radio_input.rb
index a6e45b97..d5eb942d 100644
--- a/lib/formtastic-bootstrap/inputs/radio_input.rb
+++ b/lib/formtastic-bootstrap/inputs/radio_input.rb
@@ -19,6 +19,14 @@ def to_html
end
end
+ def choice_html(choice)
+ template.content_tag(:label, label_html_options.merge(:for => choice_input_dom_id(choice), :class => nil)) do
+ builder.radio_button(input_name, choice_value(choice), input_html_options.merge(choice_html_options(choice)).merge(:required => false)) <<
+
+ choice_label(choice)
+ end
+ end
+
end
end
end
\ No newline at end of file
diff --git a/spec/inputs/check_boxes_input_spec.rb b/spec/inputs/check_boxes_input_spec.rb
new file mode 100644
index 00000000..e0946898
--- /dev/null
+++ b/spec/inputs/check_boxes_input_spec.rb
@@ -0,0 +1,439 @@
+# encoding: utf-8
+require 'spec_helper'
+
+describe 'check_boxes input' do
+
+ include FormtasticSpecHelper
+
+ describe 'for a has_many association' do
+ before do
+ @output_buffer = ''
+ mock_everything
+ Formtastic::Helpers::FormHelper.builder = FormtasticBootstrap::FormBuilder
+
+ concat(semantic_form_for(@fred) do |builder|
+ concat(builder.input(:posts, :as => :check_boxes, :value_as_class => true, :required => true))
+ end)
+ end
+
+ it_should_have_input_wrapper_with_class("check_boxes")
+ it_should_have_input_wrapper_with_class(:clearfix)
+ it_should_have_input_class_in_the_right_place
+ it_should_have_input_wrapper_with_id("author_posts_input")
+ it_should_have_a_nested_div
+ it_should_have_a_nested_div_with_class('choices.input')
+ it_should_have_a_nested_unordered_list_with_class('choices-group.inputs-list')
+ it_should_apply_error_logic_for_input_type(:check_boxes)
+ it_should_call_find_on_association_class_when_no_collection_is_provided(:check_boxes)
+ it_should_use_the_collection_when_provided(:check_boxes, 'input[@type="checkbox"]')
+
+ # TODO Refactor out the next three tests and their bretheren in radio_input_spec
+ it 'should generate a \'legend\' containing a label with text for the input' do
+ output_buffer.should have_tag('form div.clearfix label')
+ output_buffer.should have_tag('form div.clearfix label', /Posts/)
+ end
+
+ it 'should not link the \'legend\' label to any input' do
+ output_buffer.should_not have_tag('form div.clearfix > label[@for]')
+ end
+
+ it 'should generate an unordered list with a list item for each choice' do
+ output_buffer.should have_tag('form div.clearfix div.input ul')
+ output_buffer.should have_tag('form div.clearfix div.input ul li.choice', :count => ::Post.all.size)
+ end
+
+ # it 'should generate a legend containing a label with text for the input' do
+ # output_buffer.should have_tag('form li fieldset legend.label label')
+ # output_buffer.should have_tag('form li fieldset legend.label label', /Posts/)
+ # end
+ #
+ # it 'should not link the label within the legend to any input' do
+ # output_buffer.should_not have_tag('form li fieldset legend label[@for^="author_post_ids_"]')
+ # end
+ #
+ # it 'should generate an ordered list with an li.choice for each choice' do
+ # output_buffer.should have_tag('form li fieldset ol')
+ # output_buffer.should have_tag('form li fieldset ol li.choice input[@type=checkbox]', :count => ::Post.all.size)
+ # end
+
+ it 'should have one option with a "checked" attribute' do
+ output_buffer.should have_tag('form li input[@checked]', :count => 1)
+ end
+
+ it 'should not generate hidden inputs with default value blank' do
+ output_buffer.should_not have_tag("form div div ul li label input[@type='hidden'][@value='']")
+ end
+
+ it 'should not render hidden inputs inside the ol' do
+ output_buffer.should_not have_tag("form div div ul li input[@type='hidden']")
+ end
+
+ it 'should render one hidden input for each choice outside the ol' do
+ output_buffer.should have_tag("form div.clearfix > input[@type='hidden']", :count => 1)
+ end
+
+ describe "each choice" do
+
+ it 'should not give the choice label the .label class' do
+ output_buffer.should_not have_tag('li.choice label.label')
+ end
+
+ it 'should not be marked as required' do
+ output_buffer.should_not have_tag('li.choice input[@required]')
+ end
+
+ it 'should contain a label for the radio input with a nested input and label text' do
+ ::Post.all.each do |post|
+ output_buffer.should have_tag('form div.clearfix div.input ul li label', /#{post.to_label}/)
+ output_buffer.should have_tag("form div.clearfix div.input ul li label[@for='author_post_ids_#{post.id}']")
+ end
+ end
+
+ it 'should use values as li.class when value_as_class is true' do
+ ::Post.all.each do |post|
+ output_buffer.should have_tag("form div.clearfix div.input ul li.post_#{post.id} label")
+ end
+ end
+
+ it 'should have a checkbox input but no hidden field for each post' do
+ ::Post.all.each do |post|
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input#author_post_ids_#{post.id}")
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@name='author[post_ids][]']", :count => 1)
+ end
+ end
+
+ it 'should have a hidden field with an empty array value for the collection to allow clearing of all checkboxes' do
+ output_buffer.should have_tag("form div.clearfix > input[@type=hidden][@name='author[post_ids][]'][@value='']", :count => 1)
+ end
+
+ it 'the hidden field with an empty array value should be followed by the div.input and the ul' do
+ output_buffer.should have_tag("form div.clearfix > input[@type=hidden][@name='author[post_ids][]'][@value=''] + div.input ul", :count => 1)
+ end
+
+ it 'should not have a hidden field with an empty string value for the collection' do
+ output_buffer.should_not have_tag("form div.clearfix > input[@type=hidden][@name='author[post_ids]'][@value='']", :count => 1)
+ end
+
+ it 'should have a checkbox and a hidden field for each post with :hidden_field => true' do
+ output_buffer.replace ''
+
+ concat(semantic_form_for(@fred) do |builder|
+ concat(builder.input(:posts, :as => :check_boxes, :hidden_fields => true, :value_as_class => true))
+ end)
+
+ ::Post.all.each do |post|
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input#author_post_ids_#{post.id}")
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@name='author[post_ids][]']", :count => 2)
+ end
+
+ end
+
+ it "should mark input as checked if it's the the existing choice" do
+ ::Post.all.include?(@fred.posts.first).should be_true
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@checked='checked']")
+ end
+ end
+
+ describe 'and no object is given' do
+ before(:each) do
+ output_buffer.replace ''
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
+ concat(builder.input(:author_id, :as => :check_boxes, :collection => ::Author.all))
+ end)
+ end
+
+ it 'should generate a top-level div with \'legend\'' do
+ output_buffer.should have_tag('form div.clearfix > label', /Author/)
+ end
+
+ it 'shold generate an li tag for each item in the collection' do
+ output_buffer.should have_tag('form div.clearfix div.input ul li input[@type=checkbox]', :count => ::Author.all.size)
+ end
+
+ it 'should generate labels for each item' do
+ ::Author.all.each do |author|
+ output_buffer.should have_tag('form div.clearfix div.input ul li label', /#{author.to_label}/)
+ output_buffer.should have_tag("form div.clearfix div.input ul li label[@for='project_author_id_#{author.id}']")
+ end
+ end
+
+ it 'should generate inputs for each item' do
+ ::Author.all.each do |author|
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input#project_author_id_#{author.id}")
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@type='checkbox']")
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@value='#{author.id}']")
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@name='project[author_id][]']")
+ end
+ end
+
+ it 'should html escape the label string' do
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
+ concat(builder.input(:author_id, :as => :check_boxes, :collection => [["Item 1", 1], ["Item 2", 2]]))
+ end)
+
+ output_buffer.should have_tag('form div.clearfix div.input ul li label') do |label|
+ # label.body.should match /<b>Item [12]<\/b>$/
+ label.body.should match /<b>Item [12]<\/b>/
+ end
+ end
+ end
+
+ describe 'when :hidden_fields is set to false' do
+ before do
+ @output_buffer = ''
+ mock_everything
+
+ concat(semantic_form_for(@fred) do |builder|
+ concat(builder.input(:posts, :as => :check_boxes, :value_as_class => true, :hidden_fields => false))
+ end)
+ end
+
+ it 'should have a checkbox input for each post' do
+ ::Post.all.each do |post|
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input#author_post_ids_#{post.id}")
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@name='author[post_ids][]']", :count => ::Post.all.length)
+ end
+ end
+
+ it "should mark input as checked if it's the the existing choice" do
+ ::Post.all.include?(@fred.posts.first).should be_true
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@checked='checked']")
+ end
+
+ it 'should not generate empty hidden inputs' do
+ output_buffer.should_not have_tag("form div.clearfix div.input ul li label input[@type='hidden'][@value='']", :count => ::Post.all.length)
+ end
+ end
+
+ describe 'when :disabled is set' do
+ before do
+ @output_buffer = ''
+ end
+
+ describe "no disabled items" do
+ before do
+ @new_post.stub!(:author_ids).and_return(nil)
+
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:authors, :as => :check_boxes, :disabled => nil))
+ end)
+ end
+
+ it 'should not have any disabled item(s)' do
+ output_buffer.should_not have_tag("form div.clearfix div.input ul li label input[@disabled='disabled']")
+ end
+ end
+
+ describe "single disabled item" do
+ before do
+ @new_post.stub!(:author_ids).and_return(nil)
+
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:authors, :as => :check_boxes, :disabled => @fred.id))
+ end)
+ end
+
+ it "should have one item disabled; the specified one" do
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@disabled='disabled']", :count => 1)
+ output_buffer.should have_tag("form div.clearfix div.input ul li label[@for='post_author_ids_#{@fred.id}']", /fred/i)
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@disabled='disabled'][@value='#{@fred.id}']")
+ end
+ end
+
+ describe "multiple disabled items" do
+ before do
+ @new_post.stub!(:author_ids).and_return(nil)
+
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:authors, :as => :check_boxes, :disabled => [@bob.id, @fred.id]))
+ end)
+ end
+
+ it "should have multiple items disabled; the specified ones" do
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@disabled='disabled']", :count => 2)
+ output_buffer.should have_tag("form div.clearfix div.input ul li label[@for='post_author_ids_#{@bob.id}']", /bob/i)
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@disabled='disabled'][@value='#{@bob.id}']")
+ output_buffer.should have_tag("form div.clearfix div.input ul li label[@for='post_author_ids_#{@fred.id}']", /fred/i)
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@disabled='disabled'][@value='#{@fred.id}']")
+ end
+ end
+
+ end
+
+ describe "with i18n of the legend label" do
+
+ before do
+ ::I18n.backend.store_translations :en, :formtastic => { :labels => { :post => { :authors => "Translated!" }}}
+ with_config :i18n_lookups_by_default, true do
+ @new_post.stub!(:author_ids).and_return(nil)
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:authors, :as => :check_boxes))
+ end)
+ end
+ end
+
+ after do
+ ::I18n.backend.reload!
+ end
+
+ it "should do foo" do
+ output_buffer.should have_tag("div.clearfix > label", /Translated/)
+ end
+
+ end
+
+ describe "when :label option is set" do
+ before do
+ @new_post.stub!(:author_ids).and_return(nil)
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:authors, :as => :check_boxes, :label => 'The authors'))
+ end)
+ end
+
+ it "should output the correct label title" do
+ output_buffer.should have_tag("div.clearfix > label", /The authors/)
+ end
+ end
+
+ describe "when :label option is false" do
+ before do
+ @output_buffer = ''
+ @new_post.stub!(:author_ids).and_return(nil)
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:authors, :as => :check_boxes, :label => false))
+ end)
+ end
+
+ it "should output an empty \'legend\'" do
+ output_buffer.should have_tag("div.clearfix > label", "")
+ end
+
+ it "should not cause escaped HTML" do
+ output_buffer.should_not include(">")
+ end
+
+ end
+
+ describe "when :required option is true" do
+ before do
+ @new_post.stub!(:author_ids).and_return(nil)
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:authors, :as => :check_boxes, :required => true))
+ end)
+ end
+
+ it "should output the correct label title" do
+ output_buffer.should have_tag("div.clearfix > label abbr")
+ end
+ end
+
+ end
+
+ describe 'for a has_and_belongs_to_many association' do
+
+ before do
+ @output_buffer = ''
+ mock_everything
+
+ concat(semantic_form_for(@freds_post) do |builder|
+ concat(builder.input(:authors, :as => :check_boxes))
+ end)
+ end
+
+ it 'should render checkboxes' do
+ # I'm aware these two lines test the same thing
+ output_buffer.should have_tag('input[type="checkbox"]', :count => 2)
+ output_buffer.should have_tag('input[type="checkbox"]', :count => ::Author.all.size)
+ end
+
+ it 'should only select checkboxes that are present in the association' do
+ # I'm aware these two lines test the same thing
+ output_buffer.should have_tag('input[checked="checked"]', :count => 1)
+ output_buffer.should have_tag('input[checked="checked"]', :count => @freds_post.authors.size)
+ end
+
+ end
+
+ describe 'for an association when a :collection is provided' do
+ describe 'it should use the specified :member_value option' do
+ before do
+ @output_buffer = ''
+ mock_everything
+ end
+
+ it 'to set the right input value' do
+ item = mock('item')
+ item.should_not_receive(:id)
+ item.stub!(:custom_value).and_return('custom_value')
+ item.should_receive(:custom_value).exactly(3).times
+ @new_post.author.should_receive(:custom_value).exactly(3).times
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:author, :as => :check_boxes, :member_value => :custom_value, :collection => [item, item, item]))
+ end)
+ output_buffer.should have_tag('input[@type=checkbox][@value="custom_value"]', :count => 3)
+ end
+ end
+ end
+
+ describe 'when :collection is provided as an array of arrays' do
+ before do
+ @output_buffer = ''
+ mock_everything
+ @fred.stub(:genres) { ['fiction', 'biography'] }
+
+ concat(semantic_form_for(@fred) do |builder|
+ concat(builder.input(:genres, :as => :check_boxes, :collection => [['Fiction', 'fiction'], ['Non-fiction', 'non_fiction'], ['Biography', 'biography']]))
+ end)
+ end
+
+ it 'should check the correct checkboxes' do
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@value='fiction'][@checked='checked']")
+ output_buffer.should have_tag("form div.clearfix div.input ul li label input[@value='biography'][@checked='checked']")
+ end
+ end
+
+ describe "when namespace is provided" do
+
+ before do
+ @output_buffer = ''
+ mock_everything
+
+ concat(semantic_form_for(@fred, :namespace => "context2") do |builder|
+ concat(builder.input(:posts, :as => :check_boxes))
+ end)
+ end
+
+ it "should have a label for #context2_author_post_ids_19" do
+ output_buffer.should have_tag("form li label[@for='context2_author_post_ids_19']")
+ end
+
+ it_should_have_input_with_id('context2_author_post_ids_19')
+ it_should_have_input_wrapper_with_id("context2_author_posts_input")
+ end
+
+ describe "when collection is an array" do
+ before do
+ @output_buffer = ''
+ @_collection = [["First", 1], ["Second", 2]]
+ mock_everything
+ Formtastic::Helpers::FormHelper.builder = FormtasticBootstrap::FormBuilder
+
+ concat(semantic_form_for(@fred) do |builder|
+ concat(builder.input(:posts, :as => :check_boxes, :collection => @_collection))
+ end)
+ end
+
+ it "should use array items for labels and values" do
+ @_collection.each do |post|
+ output_buffer.should have_tag('form div.clearfix div.input ul li label', /#{post.first}/)
+ output_buffer.should have_tag("form div.clearfix div.input ul li label[@for='author_post_ids_#{post.last}']")
+ end
+ end
+
+ it "should not check any items" do
+ output_buffer.should have_tag('form li input[@checked]', :count => 0)
+ end
+ end
+
+end
+
diff --git a/spec/inputs/radio_input_spec.rb b/spec/inputs/radio_input_spec.rb
index 820ff7e2..b6eae59a 100644
--- a/spec/inputs/radio_input_spec.rb
+++ b/spec/inputs/radio_input_spec.rb
@@ -28,7 +28,7 @@
it_should_apply_error_logic_for_input_type(:radio)
it_should_use_the_collection_when_provided(:radio, 'input')
- it 'should generate a legend containing a label with text for the input' do
+ it 'should generate a \'legend\' containing a label with text for the input' do
output_buffer.should have_tag('form div.clearfix label')
output_buffer.should have_tag('form div.clearfix label', /Author/)
end