I'd like to reproduce this html sequence of radio buttons with simple_form in order to make simple_form work with http://semantic-ui.com/ syntax :
<div class="grouped inline fields">
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="fruit" checked="">
<label>Apples</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="fruit">
<label>Oranges</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="fruit">
<label>Pears</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="fruit">
<label>Grapefruit</label>
</div>
</div>
</div>
So I prepared a custom wrapper :
config.wrappers :semantic_radios, tag: 'div', class: "grouped fields", error_class: 'error', hint_class: 'with_hint' do |b|
b.use :html5
b.use :label
b.use :input
end
Set some options :
config.item_wrapper_tag = :div
config.item_wrapper_class = 'ui radio checkbox'
And call this code in my form :
=f.input :child_care_type, collection: [["option 1", 1],["option 2", 2]], as: :radio_buttons, wrapper: :semantic_radios
I don't know where to customize the div.field encapsulation :
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="fruit" checked="">
<label>Apples</label>
</div>
</div>
My code only render this :
<div class="ui radio checkbox">
<input type="radio" name="fruit" checked="">
<label>Apples</label>
</div>
Can you help me ? I didn't find more wrapper's customization for collection :s
Summary:
I had done something similar to this before by creating a custom input that inherits from SimpleForm::Inputs::CollectionRadioButtonsInput and overloading just a few methods. For more on custom input components, see https://github.com/plataformatec/simple_form/wiki/Adding-custom-input-components.
In any case, the code below produces your desired html markup almost exactly using simple_form v2.1.0 and rails v3.2.15.
Code:
# File: app/inputs/semantic_ui_radio_buttons_input.rb
class SemanticUiRadioButtonsInput < SimpleForm::Inputs::CollectionRadioButtonsInput
# Creates a radio button set for use with Semantic UI
def input
label_method, value_method = detect_collection_methods
iopts = {
:checked => 1,
:item_wrapper_tag => 'div',
:item_wrapper_class => 'field',
:collection_wrapper_tag => 'div',
:collection_wrapper_class => 'grouped inline fields'
}
return #builder.send(
"collection_radio_buttons",
attribute_name,
collection,
value_method,
label_method,
iopts,
input_html_options,
&collection_block_for_nested_boolean_style
)
end # method
protected
def build_nested_boolean_style_item_tag(collection_builder)
tag = String.new
tag << '<div class="ui radio checkbox">'.html_safe
tag << collection_builder.radio_button + collection_builder.label
tag << '</div>'.html_safe
return tag.html_safe
end # method
end # class
Then, in your form, just do:
-# File: app/views/<resource>/_form.html.haml
-# Define the collection
- child_care_coll = %w( Infant Toddler Preschool Kindergarten ).map!.with_index(1).to_a
-# Render the radio inputs
= f.input :child_care_type,
:collection => child_care_coll,
:label_method => :first,
:value_method => :last,
:as => :semantic_ui_radio_buttons
Results:
<div class="input semantic_ui_radio_buttons optional childcare_child_care_type">
<label class="semantic_ui_radio_buttons optional control-label">
Child care type
</label>
<div class="grouped inline fields">
<div class="field">
<div class="ui radio checkbox">
<input checked="checked" class="semantic_ui_radio_buttons optional" id="childcare_child_care_type_1" name="childcare[child_care_type]" type="radio" value="1">
<label for="childcare_child_care_type_1">Infant</label>
</div>
</div>
...
<div class="field">
<div class="ui radio checkbox">
<input class="semantic_ui_radio_buttons optional" id="childcare_child_care_type_4" name="childcare[child_care_type]" type="radio" value="4">
<label for="childcare_child_care_type_4">Kindergarten</label>
</div>
</div>
</div>
</div>
I was having trouble with the same problem until I looked through config/initializers/simple_form.rb.
Turns out you can set the option here (line ~51):
# Define the way to render check boxes / radio buttons with labels.
# Defaults to :nested for bootstrap config.
# inline: input + label
# nested: label > input
config.boolean_style = :inline
A bit further down you can also define the default wrapper tag and wrapper tag class for the collection (line ~81):
# You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
config.collection_wrapper_tag = :div
# You can define the class to use on all collection wrappers. Defaulting to none.
config.collection_wrapper_class = 'styled-radios'
Hope this helps someone :)
*using gem 'simple_form'
With updated versions 'simple_form', '~> 3.0.2' and Semantic UI 0.19.3, I achieved it with following code.
class SemanticCheckBoxesInput < SimpleForm::Inputs::CollectionCheckBoxesInput
def input
label_method, value_method = detect_collection_methods
options[:collection_wrapper_tag] = 'div'
options[:collection_wrapper_class] = 'grouped inline fields'
#builder.send("collection_check_boxes",
attribute_name, collection, value_method, label_method,
input_options, input_html_options, &collection_block_for_nested_boolean_style
)
end
protected
def item_wrapper_class
"ui checkbox field"
end
end
and in the view
= f.association :branches, as: :semantic_check_boxes
Here is the output:
Related
I'm trying to write a test for a Bootstrap form using rspec. I have the following code:
<div id="published" class="col-sm-auto">
<div class="custom-control custom-radio custom-control-inline">
<%= form.radio_button :published, true, checked: page.published?, class: 'custom-control-input' %>
<%= form.label :published, 'Published', value: true, class: 'custom-control-label' %>
</div>
<div class="custom-control custom-radio custom-control-inline">
<%= form.radio_button :published, false, checked: !page.published?, class: 'custom-control-input' %>
<%= form.label :published, 'Hidden', value: false, class: 'custom-control-label' %>
</div>
</div>
which generates the following HTML:
<div id="published" class="col-sm-auto">
<div class="custom-control custom-radio custom-control-inline">
<input class="custom-control-input" type="radio" value="true" name="page[published]" id="page_published_true" />
<label class="custom-control-label" for="page_published_true">Published</label>
</div>
<div class="custom-control custom-radio custom-control-inline">
<input class="custom-control-input" type="radio" value="false" checked="checked" name="page[published]" id="page_published_false" />
<label class="custom-control-label" for="page_published_false">Hidden</label>
</div>
</div>
When I run this test, it fails with the message
it 'should default to unpublished' do
expect(page).to have_checked_field('Hidden')
end
giving the message expected to find visible field "page[published]" that is checked and not disabled but there were no matches. Also found "", "", which matched the selector but not all filters.
Looking at the HTML in the inspector, the field is visible and not disabled, and there is a label that matches the text. I have no idea what the two empty strings are about.
Please could somebody tell me why have_checked_field isn't matching? I'm really not keen on writing a more brittle test that uses has_css or has_xpath to look for an input tag with a specific ID — the whole point is that the user should see a field labelled "Hidden" and it should be checked, regardless of what's happening behind the scenes!
I'll 99.9% guarantee you the radio input isn't actually visible and has been replaced by an image using CSS (and maybe some JS). You could do
expect(page).to have_checked_field('Hidden', visible: false)
or you could do something slightly more complicated that verifies the label is actually visible like
expect(page).to have_css('input:checked + label', exact_text: 'Hidden')
Another solution if you're dealing with a lot of these radio buttons is to write a custom selector type something like
Capybara.add_selector(:bootstrap_radio) do
xpath do |locator|
XPath.descendant(:input)[XPath.attr(:type) == 'radio'].following_sibling(:label)[XPath.string.n.is(locator)]
end
filter(:checked) do |node, value|
node.sibling('input[type="radio"]', visible: :hidden).checked? == value
end
end
which would then allow you to do
expect(page).to have_selector(:bootstrap_radio, 'Hidden', checked: true)
and then you could write helper methods like have_checked_bootstrap_radio if wanted. Note: this code was off the cuff so the XPath/CSS expressions may not be 100% correct but the general idea is sound :)
<div class="wrapper">
<div id="minHeightBlock" style="min-height: 430px;">
<div class="borderbox"><div class="standaloneBox">
<div class="sysHeaderContainer clearfix"> … </div>
<div class="notesForGuests"> … </div>
<div class="filterBox clearfix"> … </div>
<div class="resListHeader"> … </div>
<div id="corporaContainer" class="fullList">
<div id="c-a06ffa6a-dc62-4640-9760-dbd661c7ffe8" class="resItem clearfix">
<div class="resTitle">
<span id="filter-empty" class="statBall statFile empty" title="Status: Empty corpus"></span>
<span class="theText">
12321 corpora
</span>
</div>
<div class="resType"> … </div>
<div class="resSize"> … </div>
<div class="resPermission private"> … </div>
<div class="resDomain"> … </div>
<div class="resDescr"> … </div>
<div class="resDetails clearfix" style="display:none;"> … </div>
</div>
<div id="c-b8c0faba-e662-4998-836f-0ee58009b7fa" class="resItem clearfix"> … </div>
<div id="c-9d02b887-4835-4606-ad4b-775b39af9f48" class="resItem clearfix"> … </div>
<div id="c-021d3ba1-db03-4c4e-81a5-294737eb5b54" class="resItem clearfix"> … </div>
This is the code of the webpage im trying to script using Watir. All i know is only the what kind of span text the element should contain. I have many of these elements and i need to colect all of the element ID values so i can use them in further actions.
I have comented the places in the above code what i know and what i need to get.
So far i have tried this code:
#b.div(:id, "pageHeader").link(:text, "Corpora").click
sleep 5
#b.div(:id, "corporaContainer").spans(:text => /TestAuto\s.*/).each do |span|
puts span.parent.attribute_value("id")
end
But no output is done. Maybe im doing something wrong. Help me get this nut shell cracked.
Your attempt was close. The problem is that span.parent only goes up to the <div class="resTitle">. You need to go up one more parent:
#b.div(:id, "corporaContainer").spans(:text => /corpora/).each do |span|
puts span.parent.parent.attribute_value("id")
end
(Note that I changed the text in the locator of the spans since TestAuto\s.* did not match the sample html.)
Alternatively, I sometimes find it better to find the divs that contain the span. This way you do not have to worry about the number of parents changing:
p #b.divs(:class => 'resItem')
.find_all { |div| div.span(:text => /corpora/).exists? }
.collect { |div| div.id }
#=> ["c-a06ffa6a-dc62-4640-9760-dbd661c7ffe8"]
Below is a working example. Note that there are 2 important things:
The list of results is loaded asynchronously. Therefore you need to wait for the list to finish loading before capturing the results. sleep(5) might work, but you are better off using an actual wait method (since it seems to take longer than 5 seconds).
Make sure the search text actually exists on the page. In the below example, there is no "12321 corpora" title that was mentioned in the sample html.
Example:
require 'watir-webdriver'
# Title to search for:
title_text = /UniAdm/
# Go to the Corpora page:
#b = Watir::Browser.new :ff
#b.goto "https://www.letsmt.eu/Corpora.aspx"
# Wait for the results to load:
container = #b.div(:id, "corporaContainer")
container.div(:class => 'resItem').wait_until_present
# Find the matching ids:
p container.divs(:class => 'resItem')
.find_all { |div| div.span(:class => 'theText', :text => title_text).exists? }
.collect { |div| div.id }
#=> ["c-87ee80a9-e529-48b2-92be-bc8d76375478", "c-f139e781-4789-41f9-82e8-914e0e3eff81", "c-e17641d2-9364-4e87-9047-ba35580dc32f"]
I'm trying to mechanize the select devices part of the Apple Dev Portal "Edit iOS Provisioning Profile", which can be found here (if you're logged in).
The source looks like this:
<form name="profileEdit" method="get" action="https://developer.apple.com/services-developerportal/QH43B2/account/ios/profile/regenProvisioningProfile.action?content-type=text/x-url-arguments&accept=application/json&requestId=838c910b-f63d-843e7b1ce126&userLocale=en_US&teamId=BF5K33D" successURL="/account/ios/profile/profileDownload.action?provisioningProfileId=">
<input type="hidden" name="distributionType" value='store'/>
<input type="hidden" name="returnFullObjects" value="false"/>
<div class="nameSection">
<dl>
<dt class="selectDevices">Devices:</dt>
<dd class="selectDevices">
<div class="table">
<div class="rows">
<div><input type="checkbox" name="deviceIds" class="validate" value="8T8RG7HX" id="devices-6" ><span class="title">iPhone 4 - JC</span></div>
<div><input type="checkbox" name="deviceIds" class="validate" value="7Y9F8N47" id="devices-7" ><span class="title">iPhone 5 - DP</span></div>
<div><input type="checkbox" name="deviceIds" class="validate" value="ZNES97W7" id="devices-8" checked><span class="title">iPhone 5 - JC</span></div>
<div><input type="checkbox" name="deviceIds" class="validate" value="CRDSL7S5" id="devices-9" checked><span class="title">iPod 4 inch</span></div>
</div>
</div>
</dd>
<dd class="form-error deviceIds hidden">Please select a Device</dd>
</dl>
</div>
<div class="bottom-buttons">
<a class="button small left cancel"><span>Cancel</span></a>
<a class="button small blue right submit"><span>Generate</span></a>
</div>
</form>
What I want to do is check all boxes:
form = page.form_with(:name => 'profileEdit') or raise UnexpectedContentError
form.checkboxes_with(:name => 'deviceIds').each do |checkbox|
puts checkbox["id"] # prints correct value of devices-6...
checkbox.check
end
form.method = 'GET'
form.submit
I get no run time errors, however when I refresh the actual page, not all checkboxes are checked as I intended. Am I missing something?
For this:
form.checkboxes_with(:name => 'deviceIds').each do |checkbox|
puts checkbox["id"] # prints correct value of devices-6...
checkbox.check
end
What do these result in:
tmp1 = form.checkboxes_with(:name => 'deviceIds').map { |cb| cb.check }
tmp2 = form.checkboxes_with(:name => 'deviceIds').map { |cb| cb.checked? }
I would expect [true, true, true, true] for both. If not, something is clearing those out. The check() method is implemented in RadioButton which does clear the checked state of all buttons of the same name, but it should be limited to radiobutton types. The checked attribute is writable by itself, so you could try directly writing it:
form.checkboxes_with(:name => 'deviceIds').each do |cb|
cb.checked = true
end
And avoid what may be a bug or inconsistency in that page/mechanize/whatever. Just a guess, but something to try.
As for as I have understood, your issue is that you are visiting the actual page after setting checkboxes which will not work. But if you inspect the results returned after submit you will realize that Mechanize has set the check boxes and returned the response.
If you want to visually see that in an actual browser, you may need to use Watir / Webdriver etc.
Using Simple_form 2.0.2
The simple form code using HAML:
= f.input :remember_me, as: :boolean, inline_label: 'Remember me'
But it renders this:
<div class="control-group boolean optional">
<label class="boolean optional control-label" for="admin_remember_me">Remember me</label>
<div class="controls">
<input name="admin[remember_me]" type="hidden" value="0" />
<label class="checkbox"><input class="boolean optional" id="admin_remember_me" name="admin[remember_me]" type="checkbox" value="1" />Remember me</label>
</div>
</div>
How do I remove that first label that's rendered, so that I only have the inline label?
You can use:
= f.input :remember_me, as: :boolean, inline_label: 'Remember me', label: false
Found a solution after much Google fu.
Use input_field instead of input which won't automatically generate a label.
= f.input_field :remember_me, as: :boolean, inline_label: 'Remember me'
For whom it doesn't work
= f.input_field ...
Use this way
= f.check_box ...
With simple_form 2.1.0 and rails 3.0.20, none of the solutions listed here worked (I don't want to use f.input_field because it's an admission of defeat).
The missing part is the boolean_style option:
options.merge!({label: false, boolean_style: :inline})
I suggest you create a custom input for this (e.g.: inline_checkbox)
boolean_style is configured as :nested by default, I think:
# Defaults to :nested for bootstrap config.
# :inline => input + label
# :nested => label > input
config.boolean_style = :nested
.control-group.error .help-inline {
display: none;
}
This should work, it works for me on rails 3.2 and simple_form 2.x+
Maybe too late, but inspired by gamov answer I have made this a custom wrapper from inline bootstrap checkbox in the initializer file 'config/simple_form_bootstrap.rb':
config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'col-sm-3 control-label'
b.use :input
b.use :error, wrap_with: { tag: 'span', class: 'help-block' }
b.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
end
which generates this html:
<div class="form-group boolean optional user_admin">
<label class="boolean optional col-sm-3 control-label" for="user_admin">Admin</label>
<div class="col-sm-9 checkbox-inline">
<input name="user[admin]" value="0" type="hidden">
<input class="boolean optional" id="user_admin" name="user[admin]" value="1" type="checkbox">
</div>
I have a haml like ;
= form_for #company, :html => {:multipart => true}, :url => update_user_company_path do |f|
.field
Title:#{f.text_field :name}
= f.fields_for :attachments do |builder|
- if builder.object.new_record?
.field
= builder.hidden_field :name, :value => 'logo'
= builder.file_field :file
- elsif builder.object.name.eql?('logo') && !builder.object.file.url.eql?('/files/original/missing.png')
.field
%span.thumbnail
= link_to "Delete", delete_company_attachment_path(#company, builder.object), :method => :delete, :class => "remove_image"
= image_tag builder.object.file.url, :style => "height:86px;width:125px"
= f.submit 'Ok'
Chrome renders this code as intended, but in Firefox it is like;
<form method="post" id="edit_company_29" enctype="multipart/form-data" class="edit_company" action="/users/25/company" accept-charset="UTF-8"><div style="margin:0;padding:0;display:inline"><input type="hidden" value="✓" name="utf8"><input type="hidden" value="put" name="_method"><input type="hidden" value="thisismytokenvalue=" name="authenticity_token"></div>
<div class="field">
Title:<input type="text" value="sdgdfgghjh" size="30" name="company[name]" id="company_name">
</div>
<input id="company_attachments_attributes_0_id" name="company[attachments_attributes][0][id]" type="hidden" value="114" /><input id="company_attachments_attributes_1_id" name="company[attachments_attributes][1][id]" type="hidden" value="115" /><div class="field">
<input type="hidden" value="logo" name="company[attachments_attributes][2][name]" id="company_attachments_attributes_2_name">
<input type="file" name="company[attachments_attributes][2][file]" id="company_attachments_attributes_2_file">
</div>
<input type="submit" value="Ok" name="commit">
</form>
Why is an element escaped. If you check haml, you can see I didn't put them.
Where did it come from?
Why is it happening?
Wow ... I've just had a similar issue. My guess is that if you pass nil to the form_builder's fields_for the hidden_input isn't returned with html_safe. To quickly fix that add a
-else
=""
after the whole elsif block
You must return something other than nil in the fields_for block.
This issue has been fixed in Haml 4.0.4 by mattwildig, with the help of #lulalala. Here is an explanation of the problem:
The #fields_for helper generates a hidden input field for each record, which it adds to the result of capturing the block. Normally the return value of capture will be a SafeBuffer, so this won’t be escaped. However if the block doesn’t write anything to the buffer then Haml returns a literal empty String from capture. The hidden input element is concatted to this string, and when it is combined with the rest of the entries, since it is a String being added to a SafeBuffer, it gets escaped.