Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

input groups #839

Closed
wants to merge 4 commits into from

2 participants

@trimentor

Hi Justin,

In one of our projects we needed a way to create input groups (prepend and append stuff to our fields) to give our inputs more context. Instead of repeating myself over and over again I created a new Formtastic Input called IconishSegmentsInput.

I documented the code, added specs, basic CSS styles and examples.

Please take a look at the code and feel free to ask questions, modify and use the code.

Keep up the good work!

With kind regards,
Kjel

@justinfrench
Owner

Hi, this looks interesting! I'm not going to merge it in as-is, but would like to keep talking about it if you have time!

  1. I think it's plausible that you would want this functionality on many of the input types that currently mixin Stringish, so I'm wondering if maybe this should be functionality that's mixed into the existing inputs, rather than a new type of custom input. Probably just starting with some of the Stringish inputs?

  2. From there, it would be good to think about the option names & API, as there's potential overlap with the idea of generic :prefix and (just inside the li wrapper at the start) and :suffix (just before the </li>) that have been on my radar for a while.

  3. Out of the box, the CSS should probably be a little less heavy-handed and opinionated on styling — we usually aim for the most minimal representation we can, and rounded borders is probably too much of an embellishment.

Anyway, interested in your thoughts on #1 mostly to start with.

@trimentor

Hi Justin,

Thank you for your time, interest and feedback!

1) I think you're right. I'll extract my code in a new module, suggestions (naming, etc.) are always welcome!

2) Sounds good! I would like to know more about your idea. What do you think about renaming the options :input_prepend and :input_append into :input_prefix and :input_suffix?

3) I'll remove border-radius and border-color.

@justinfrench
Owner

Closing due to lack of activity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 12, 2012
  1. @trimentor

    added IconishSegmentsInput support: use :as => :iconish_segments to c…

    trimentor authored
    …reate input groups
    
    Input groups provide an easy way to give more context to your inputs.
  2. @trimentor
Commits on Apr 13, 2012
  1. @trimentor
Commits on Apr 14, 2012
  1. @trimentor

    added basic styling for IconishSegmentsInput and added two examples t…

    trimentor authored
    …o sample/basic_inputs.html
This page is out of date. Refresh to see the latest.
View
76 app/assets/stylesheets/formtastic.css
@@ -286,3 +286,79 @@ This stylesheet forms part of the Formtastic Rails Plugin
padding:0;
}
+
+/* ICONISH SEGMENTS INPUTS
+--------------------------------------------------------------------------------------------------*/
+.formtastic .iconish_segments .input-prepend .add-on {
+ -moz-border-radius-topleft: 5px;
+ -moz-border-radius-bottomleft: 5px;
+ -webkit-border-top-left-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+ -khtml-border-top-left-radius: 5px;
+ -khtml-border-bottom-left-radius: 5px;
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+ margin-right: -1px;
+}
+.formtastic .iconish_segments .input-append .add-on,
+.formtastic .iconish_segments .input-append button {
+ -moz-border-radius-topright: 5px;
+ -moz-border-radius-bottomright: 5px;
+ -moz-border-radius-topleft: 0;
+ -moz-border-radius-bottomleft: 0;
+ -webkit-border-top-right-radius: 5px;
+ -webkit-border-bottom-right-radius: 5px;
+ -webkit-border-top-left-radius: 0;
+ -webkit-border-bottom-left-radius: 0;
+ -khtml-border-top-right-radius: 5px;
+ -khtml-border-bottom-right-radius: 5px;
+ -khtml-border-top-left-radius: 0;
+ -khtml-border-bottom-left-radius: 0;
+ border-top-right-radius: 5px;
+ border-bottom-right-radius: 5px;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ margin-left: -1px;
+}
+.formtastic .iconish_segments .input-prepend.input-append .add-on:first-child {
+ -moz-border-radius-topleft: 5px;
+ -moz-border-radius-bottomleft: 5px;
+ -moz-border-radius-topright: 0;
+ -moz-border-radius-bottomright: 0;
+ -webkit-border-top-left-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+ -webkit-border-top-right-radius: 0;
+ -webkit-border-bottom-right-radius: 0;
+ -khtml-border-top-left-radius: 5px;
+ -khtml-border-bottom-left-radius: 5px;
+ -khtml-border-top-right-radius: 0;
+ -khtml-border-bottom-right-radius: 0;
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ margin-left: 0;
+ margin-right: -1px;
+}
+
+.formtastic .iconish-segments-controls {
+ float: left;
+ display: block;
+ width: 72%;
+}
+.formtastic .iconish-segments-controls > * {
+ float: left;
+ display: block;
+ padding: 4px;
+ border: 1px solid #CCC;
+ line-height: 18px;
+}
+.formtastic .iconish-segments-controls .add-on,
+.formtastic .iconish-segments-controls button {
+ background-color: #EEE;
+ color: inherit;
+ font-size: 13px;
+}
+.formtastic .iconish-segments-controls input {
+ height: 18px;
+}
View
1  lib/formtastic/helpers/input_helper.rb
@@ -105,6 +105,7 @@ module InputHelper
# * `:time_zone` (see {Inputs::TimeZoneInput})
# * `:time_select` (see {Inputs::TimeSelectInput})
# * `:url` (see {Inputs::UrlInput})
+ # * `:iconish_segments` (see {Inputs::IconishSegmentsInput})
#
# Calling `:as => :string` (for example) will call `#to_html` on a new instance of
# `Formtastic::Inputs::StringInput`. Before this, Formtastic will try to instantiate a top-level
View
1  lib/formtastic/inputs.rb
@@ -32,6 +32,7 @@ module Inputs
autoload :TimeZoneInput
autoload :Timeish
autoload :UrlInput
+ autoload :IconishSegmentsInput
end
end
View
151 lib/formtastic/inputs/iconish_segments_input.rb
@@ -0,0 +1,151 @@
+module Formtastic
+ module Inputs
+
+ # Outputs a simple `<label> with input groups `<div class="iconish-segments-controls">` wrapped in the standard
+ # `<li>` wrapper. Input groups provide an easy way to give more context to your inputs. Context is given by
+ # appending and/or prepending `<span class="add-on">` to `<input type="text">`. If you need to render a button
+ # next to your field just pass in a code block with `:input_append => lambda { button_tag }`. This will render
+ # `<button>` instead of `<span class="add-on">`. It is also possible to render an image inside
+ # `<span class="add-on">` with `:input_prepend => lambda { image_tag("icon.png") }`. This will output a simple
+ # `<img />` wrapped in the default `<span class="add-on"` wrapper.
+ #
+ # @example Full form context and output
+ #
+ # <= semantic_form_for(@bank_account) do |f| %>
+ # <%= f.inputs do %>
+ # <%= f.input :money, :as => :iconish_segments, :input_prepend => '$', :input_append => '.00' %>
+ # <% end %>
+ # <% end %>
+ #
+ # <form...>
+ # <fieldset...>
+ # <ol>
+ # <li... class="iconish_segments">
+ # <label... for="bank_account_money"><Money/label>
+ # <div class="iconish-segments-controls input-prepend input-append">
+ # <span class="add-on">$</span>
+ # <input type="text" id="bank_account_money" name="bank_account[money]" />
+ # <span class="add-on">.00</span>
+ # </div>
+ # </li>
+ # </ol>
+ # </fieldset>
+ # </form>
+ #
+ # @example Pass in a block
+ #
+ # <= semantic_form_for(@bank_account) do |f| %>
+ # <%= f.inputs do %>
+ # <%= f.input :money, :as => :iconish_segments, :input_prepend => lambda { image_tag("money.png") } %>
+ # <% end %>
+ # <% end %>
+ #
+ # <form...>
+ # <fieldset...>
+ # <ol>
+ # <li... class="iconish_segments">
+ # <label... for="bank_account_money"><Money/label>
+ # <div class="iconish-segments-controls input-prepend">
+ # <span class="add-on">
+ # <img src="/images/money.png" alt="Money" />
+ # </span>
+ # <input type="text" id="bank_account_money" name="bank_account[money]" />
+ # </div>
+ # </li>
+ # </ol>
+ # </fieldset>
+ # </form>
+ #
+ # @example Append with button
+ #
+ # <= semantic_form_for(@user) do |f| %>
+ # <%= f.inputs do %>
+ # <%= f.input :mobile, :as => :iconish_segments, :input_append => lambda { button_tag("Send", :disable_with => "Sending...") } %>
+ # <% end %>
+ # <% end %>
+ #
+ # <form...>
+ # <fieldset...>
+ # <ol>
+ # <li... class="iconish_segments">
+ # <label... for="user_mobile"><Money/label>
+ # <div class="iconish-segments-controls input-append">
+ # <input type="text" id="user_mobile" name="user[mobile]" />
+ # <button data-disable-with="Sending..." name="button" type="submit">Send</button>
+ # </div>
+ # </li>
+ # </ol>
+ # </fieldset>
+ # </form>
+ #
+ # @see Formtastic::Helpers::InputsHelper#input InputsHelper#input for full documentation of all possible options.
+ class IconishSegmentsInput
+ include Base
+ include Base::Stringish
+
+ def to_html
+ input_wrapping do
+ label_html <<
+ iconish_segments_controls
+ end
+ end
+
+ def iconish_segments_controls
+ iconish_segments_wrapping do
+ builder.text_field(method, input_html_options)
+ end
+ end
+
+ def iconish_segments_wrapping(&block)
+ template.content_tag(:div,
+ iconish_segments(&block),
+ iconish_segments_wrapping_html_options
+ )
+ end
+
+ def iconish_segments(&block)
+ [add_on_prepend, template.capture(&block), add_on_append].compact.join("\n").html_safe
+ end
+
+ def iconish_segments_wrapping_html_options
+ {:class => iconish_segments_wrapping_classes}
+ end
+
+ def iconish_segments_wrapping_classes
+ opt = options
+ classes = ['iconish-segments-controls']
+ classes << 'input-prepend' if opt[:input_prepend]
+ classes << 'input-append' if opt[:input_append]
+ classes.join(' ')
+ end
+
+ def add_on_prepend
+ add_on_from_options(:input_prepend)
+ end
+
+ def add_on_append
+ add_on_from_options(:input_append)
+ end
+
+ def add_on_from_options(key)
+ if opt = options[key]
+ content = if opt.is_a?(Proc)
+ opt.call
+ else
+ opt.to_s
+ end
+ add_on(content)
+ end
+ end
+
+ def add_on(content)
+ if content =~ /^<button.*>/
+ content
+ else
+ template.content_tag(:span, content, :class => 'add-on')
+ end
+ end
+ end
+
+ end
+end
View
17 sample/basic_inputs.html
@@ -187,6 +187,23 @@
</ol>
</fieldset>
</li>
+
+ <li id="gem_money_input" class="iconish_segments input optional stringish">
+ <label for="gem_money" class=" label">Money</label>
+ <div class="iconish-segments-controls input-prepend input-append">
+ <span class="add-on">$</span>
+ <input type="text" name="gem[money]" id="gem_money">
+ <span class="add-on">.00</span>
+ </div>
+ </li>
+
+ <li id="gem_text_message_input" class="iconish_segments input optional stringish">
+ <label for="gem_text_message" class=" label">Text Message</label>
+ <div class="iconish-segments-controls input-prepend input-append">
+ <input type="tel" name="gem[text_message]" id="gem_text_message">
+ <button>Send</button>
+ </div>
+ </li>
</ol>
</fieldset>
View
158 spec/inputs/iconish_segments_input_spec.rb
@@ -0,0 +1,158 @@
+# encoding: utf-8
+require 'spec_helper'
+
+describe 'iconish segments input' do
+
+ include FormtasticSpecHelper
+
+ before do
+ @output_buffer = ''
+ mock_everything
+ end
+
+ describe "when object is provided" do
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:money, :as => :iconish_segments))
+ end)
+ end
+
+ it_should_have_input_wrapper_with_class(:iconish_segments)
+ it_should_have_input_wrapper_with_class(:input)
+ it_should_have_input_wrapper_with_class(:stringish)
+ it_should_have_input_wrapper_with_id("post_money_input")
+ it_should_have_label_with_text(/Money/)
+ it_should_have_label_for("post_money")
+ it_should_have_input_with_id("post_money")
+ it_should_have_input_with_type(:text)
+ it_should_have_input_with_name("post[money]")
+ it_should_apply_custom_input_attributes_when_input_html_provided(:string)
+ it_should_apply_error_logic_for_input_type(:string)
+
+ it "should have input groups wrapper with class 'iconish-segments-controls'" do
+ output_buffer.should have_tag("form li div.iconish-segments-controls")
+ end
+
+ end
+
+ context "when :input_prepend is provided" do
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:money, :as => :iconish_segments, :input_prepend => ''))
+ end)
+ end
+
+ it "should have input groups wrapper with class 'iconish-segments-controls input-prepend'" do
+ output_buffer.should have_tag("form li div.iconish-segments-controls.input-prepend")
+ end
+
+ it "should have a span with class 'add-on'" do
+ output_buffer.should have_tag("form li div span.add-on")
+ end
+
+ it "prepends a span element to the input" do
+ output_buffer.should have_tag("form li div span + input")
+ end
+
+ context "and is a String" do
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:money, :as => :iconish_segments, :input_prepend => '$'))
+ end)
+ end
+
+ it "should have a span with text '$'" do
+ output_buffer.should have_tag("form li div span", '$')
+ end
+
+ end
+
+ context "and is a Proc" do
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:money, :as => :iconish_segments, :input_prepend => lambda { '$' }))
+ end)
+ end
+
+ it "should have a span containing the Proc's output" do
+ output_buffer.should have_tag("form li div span", '$')
+ end
+
+ end
+
+ end
+
+ context "when :input_append is provided" do
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:money, :as => :iconish_segments, :input_append => ''))
+ end)
+ end
+
+ it "should have input groups wrapper with class 'iconish-segments-controls input-append'" do
+ output_buffer.should have_tag("form li div.iconish-segments-controls.input-append")
+ end
+
+ it "should have a span with class 'add-on'" do
+ output_buffer.should have_tag("form li div span.add-on")
+ end
+
+ it "appends a span element to the input" do
+ output_buffer.should have_tag("form li div input + span")
+ end
+
+ context "and is a String" do
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:money, :as => :iconish_segments, :input_append => '.00'))
+ end)
+ end
+
+ it "should have a span with text '.00'" do
+ output_buffer.should have_tag("form li div span", '.00')
+ end
+
+ end
+
+ context "and is a Proc" do
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:money, :as => :iconish_segments, :input_append => lambda { '.00' }))
+ end)
+ end
+
+ it "should have a span containing the Proc's output" do
+ output_buffer.should have_tag("form li div input + span", '.00')
+ end
+
+ end
+
+ context "and is a Proc that outputs a button" do
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:money, :as => :iconish_segments, :input_append => lambda { button_tag('Transfer') }))
+ end)
+ end
+
+ it "should have a button with text 'Send'" do
+ output_buffer.should have_tag("form li div input + button", 'Transfer')
+ end
+
+ end
+
+ end
+
+ context "when both :input_prepend and :input_append are provided" do
+ before do
+ concat(semantic_form_for(@new_post) do |builder|
+ concat(builder.input(:money, :as => :iconish_segments, :input_prepend => '', :input_append => ''))
+ end)
+ end
+
+ it "should have input groups wrapper with class 'iconish-segments-controls input-prepend input-append'" do
+ output_buffer.should have_tag("form li div.iconish-segments-controls.input-prepend.input-append")
+ end
+
+ end
+
+end
View
2  spec/spec_helper.rb
@@ -361,6 +361,7 @@ def new_author_path(*args); "/authors/new"; end
@new_post.stub!(:country)
@new_post.stub!(:country_subdivision)
@new_post.stub!(:country_code)
+ @new_post.stub!(:money)
@new_post.stub!(:document).and_return(@mock_file)
@new_post.stub!(:column_for_attribute).with(:meta_description).and_return(mock('column', :type => :string, :limit => 255))
@new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :string, :limit => 50))
@@ -378,6 +379,7 @@ def new_author_path(*args); "/authors/new"; end
@new_post.stub!(:column_for_attribute).with(:phone).and_return(mock('column', :type => :string, :limit => 255))
@new_post.stub!(:column_for_attribute).with(:search).and_return(mock('column', :type => :string, :limit => 255))
@new_post.stub!(:column_for_attribute).with(:document).and_return(nil)
+ @new_post.stub!(:column_for_attribute).with(:money).and_return(mock('column', :type => :string, :limit => 255))
@new_post.stub!(:author).and_return(@bob)
@new_post.stub!(:author_id).and_return(@bob.id)
Something went wrong with that request. Please try again.