twitter_bootstrap_formalwear 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.markdown +22 -0
- data/README.markdown +142 -0
- data/Rakefile +38 -0
- data/lib/tasks/twitter_bootstrap_formalwear_tasks.rake +4 -0
- data/lib/twitter_bootstrap_formalwear.rb +10 -0
- data/lib/twitter_bootstrap_formalwear/form_builder.rb +256 -0
- data/lib/twitter_bootstrap_formalwear/form_helpers.rb +30 -0
- data/lib/twitter_bootstrap_formalwear/railtie.rb +9 -0
- data/lib/twitter_bootstrap_formalwear/version.rb +3 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +56 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/test_helper.rb +15 -0
- data/test/twitter_bootstrap_formalwear_test.rb +7 -0
- metadata +162 -0
data/LICENSE.markdown
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
===============
|
3
|
+
|
4
|
+
Copyright (C) 2011 by Stephen Touset
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
Twitter Bootstrap Formalwear
|
2
|
+
============================
|
3
|
+
|
4
|
+
Twitter Bootstrap Formalwear dresses up your Ruby on Rails forms with
|
5
|
+
[Twitter Bootstrap] markup. It is a lightly-modified fork of
|
6
|
+
[`twitter_bootstrap_form_for`](https://github.com/stouset/twitter_bootstrap_form_for).
|
7
|
+
|
8
|
+
Unlike `twitter_bootstrap_form_for`, which overwrites existing [Rails FormHelper] methods,
|
9
|
+
`twitter_bootstrap_formalwear` extends FormHelper with additional methods to generate
|
10
|
+
labels, controls, control groups, and action blocks.
|
11
|
+
|
12
|
+
Because existing methods remain intact, you maintain the option to generate
|
13
|
+
non-Bootstrap markup in your form if necessary.
|
14
|
+
|
15
|
+
|
16
|
+
## Dependencies ##
|
17
|
+
|
18
|
+
Rails and Twitter Bootstrap (v2.0+), of course. A familiarity with Rails FormHelper
|
19
|
+
usage and syntax will be helpful.
|
20
|
+
|
21
|
+
|
22
|
+
## Installation ##
|
23
|
+
|
24
|
+
Add the dependency to your `Gemfile`:
|
25
|
+
|
26
|
+
gem 'twitter_bootstrap_formalwear'
|
27
|
+
|
28
|
+
Then run `bundle` from the project directory.
|
29
|
+
|
30
|
+
|
31
|
+
## Usage/Syntax ##
|
32
|
+
|
33
|
+
|
34
|
+
### Initializing a Form: ###
|
35
|
+
|
36
|
+
To use `twitter_bootstrap_formalwear`, first create a form using the same syntax as `form_for` or `fields_for`:
|
37
|
+
|
38
|
+
<%= formalwear_form_for (@person, :html => { :class => 'form-horizontal' }) do |f| %>
|
39
|
+
|
40
|
+
<%= formalwear_fields_for (:person, @client do |f| %>
|
41
|
+
|
42
|
+
|
43
|
+
### Generating Form Elements: ###
|
44
|
+
|
45
|
+
`twitter_bootstrap_formalwear` extends `form_for` and `fields_for` with additional methods:
|
46
|
+
|
47
|
+
- group
|
48
|
+
- label
|
49
|
+
- controls
|
50
|
+
- actions
|
51
|
+
|
52
|
+
These methods can be used generically to create container elements:
|
53
|
+
|
54
|
+
<%= f.group do %>
|
55
|
+
<!-- HTML goes here! -->
|
56
|
+
<% end %>
|
57
|
+
|
58
|
+
# =>
|
59
|
+
<div class="control-group">
|
60
|
+
<!-- HTML goes here! -->
|
61
|
+
</div>
|
62
|
+
|
63
|
+
Or, more usefully, they can be prepended with a form element (of any type supported by the [Rails FormHelper]):
|
64
|
+
|
65
|
+
<%= f.text_field_group :first_name, :class => 'input-large' %>
|
66
|
+
|
67
|
+
# =>
|
68
|
+
<div class="control-group">
|
69
|
+
<label class="control-label" for="first_name">First name</label>
|
70
|
+
<div class="controls">
|
71
|
+
<input type="text" name="first_name" value="" class="input-large" />
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
|
75
|
+
If you need fine-grained control, you can generate only a Bootstrap label:
|
76
|
+
|
77
|
+
<%= f.text_field_label :first_name %>
|
78
|
+
|
79
|
+
# =>
|
80
|
+
<label class="control-label" for="first_name">First name</label>
|
81
|
+
|
82
|
+
...or a Bootstrap control:
|
83
|
+
|
84
|
+
<%= f.text_field_control :first_name, :class => 'input-large' %>
|
85
|
+
|
86
|
+
# =>
|
87
|
+
<div class="controls">
|
88
|
+
<input type="text" name="first_name" value="" class="input-large" />
|
89
|
+
</div>
|
90
|
+
|
91
|
+
For you control freaks, the standard Rails FormHelper methods still work if needed:
|
92
|
+
|
93
|
+
<%= f.text_field :first_name, :class => 'input-large' %>
|
94
|
+
|
95
|
+
# =>
|
96
|
+
<input type="text" name="first_name" value="" class="input-large" />
|
97
|
+
|
98
|
+
|
99
|
+
### More Usage Examples ###
|
100
|
+
|
101
|
+
Form actions:
|
102
|
+
|
103
|
+
<%= f.actions do %>
|
104
|
+
<%= f.submit 'Sign up', :class => 'btn' %>
|
105
|
+
<% end %>
|
106
|
+
|
107
|
+
# =>
|
108
|
+
<div class="form-actions">
|
109
|
+
<input class="btn" type="submit" value="Sign up">
|
110
|
+
</div>
|
111
|
+
|
112
|
+
Checkboxes (note that omitting the first argument will omit the label element):
|
113
|
+
|
114
|
+
<%= f.group do %>
|
115
|
+
<%= f.check_box :remember_me %> Remember Me?
|
116
|
+
<% end %>
|
117
|
+
|
118
|
+
# =>
|
119
|
+
<div class="control-group">
|
120
|
+
<div class="controls">
|
121
|
+
<input id="remember_me" name="remember_me" type="checkbox" value="1" /> Remember Me?
|
122
|
+
</div>
|
123
|
+
</div>
|
124
|
+
|
125
|
+
Add-ons and form help text: may or may not work yet. Documentation coming soon.
|
126
|
+
|
127
|
+
## Bugs / Known Issues ##
|
128
|
+
|
129
|
+
You may have already noticed that the `label` method exists in `form_for`, even
|
130
|
+
though I claim above that `twitter_bootstrap_formalwear` doesn't overwrite existing
|
131
|
+
code. But since the new method dances so lightly (just adding `class="control-label"`),
|
132
|
+
I think it's safe to simply overwrite the old method. Thoughts?
|
133
|
+
|
134
|
+
I've been using this fork successfully on my own projects, but some less common
|
135
|
+
use cases may be buggy.
|
136
|
+
|
137
|
+
## Todo ##
|
138
|
+
|
139
|
+
Tests!
|
140
|
+
|
141
|
+
[Twitter Bootstrap]: http://twitter.github.com/bootstrap/
|
142
|
+
[Rails FormHelper]: http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'TwitterBootstrapFormalwear'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
|
30
|
+
Rake::TestTask.new(:test) do |t|
|
31
|
+
t.libs << 'lib'
|
32
|
+
t.libs << 'test'
|
33
|
+
t.pattern = 'test/**/*_test.rb'
|
34
|
+
t.verbose = false
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
task :default => :test
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
|
3
|
+
module TwitterBootstrapFormalwear
|
4
|
+
autoload :FormBuilder, 'twitter_bootstrap_formalwear/form_builder'
|
5
|
+
autoload :FormHelpers, 'twitter_bootstrap_formalwear/form_helpers'
|
6
|
+
autoload :Railtie, 'twitter_bootstrap_formalwear/railtie'
|
7
|
+
autoload :VERSION, 'twitter_bootstrap_formalwear/version'
|
8
|
+
end
|
9
|
+
|
10
|
+
TwitterBootstrapFormalwear::Railtie # trigger loading the Railtie
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'twitter_bootstrap_formalwear'
|
2
|
+
require 'action_view/helpers'
|
3
|
+
|
4
|
+
class TwitterBootstrapFormalwear::FormBuilder < ActionView::Helpers::FormBuilder
|
5
|
+
include TwitterBootstrapFormalwear::FormHelpers
|
6
|
+
|
7
|
+
attr_reader :template
|
8
|
+
attr_reader :object
|
9
|
+
attr_reader :object_name
|
10
|
+
|
11
|
+
INPUTS = [
|
12
|
+
:check_box,
|
13
|
+
:radio_button,
|
14
|
+
:select,
|
15
|
+
*ActionView::Helpers::FormBuilder.instance_methods.grep(%r{
|
16
|
+
_(area|field|select)$ # all area, field, and select methods
|
17
|
+
}mx).map(&:to_sym)
|
18
|
+
]
|
19
|
+
|
20
|
+
INPUTS.delete(:hidden_field)
|
21
|
+
|
22
|
+
#TOGGLES = [
|
23
|
+
# :check_box,
|
24
|
+
# :radio_button,
|
25
|
+
#]
|
26
|
+
|
27
|
+
#
|
28
|
+
# Wraps the contents of the block passed in a fieldset with optional
|
29
|
+
# +legend+ text.
|
30
|
+
#
|
31
|
+
def fieldset(legend = nil, options = {})
|
32
|
+
template.content_tag(:fieldset, options) do
|
33
|
+
template.concat template.content_tag(:legend, legend) unless legend.nil?
|
34
|
+
yield
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Wraps action buttons into their own styled container.
|
40
|
+
#
|
41
|
+
def actions(&block)
|
42
|
+
template.content_tag(:div, :class => 'form-actions', &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Generates a control-group div element for the given +attribute+,
|
47
|
+
# containing label and controls elements.
|
48
|
+
#
|
49
|
+
def group(attribute = '', text = '', options = {}, &block)
|
50
|
+
text, attribute = attribute, nil if attribute.kind_of? String
|
51
|
+
|
52
|
+
id = _wrapper_id attribute, 'control_group'
|
53
|
+
classes = _wrapper_classes attribute, 'control-group'
|
54
|
+
|
55
|
+
template.content_tag(:div, :id => id, :class => classes) do
|
56
|
+
if !attribute.blank?
|
57
|
+
template.concat self.label(attribute, text, options, &block)
|
58
|
+
end
|
59
|
+
template.concat self.controls(attribute, text, options, &block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Generates a label element for the given +attribute+. If +text+ is passed, uses
|
65
|
+
# that as the text for the label; otherwise humanizes the +attribute+ name.
|
66
|
+
#
|
67
|
+
def label(attribute, text = '', options = {}, &block)
|
68
|
+
text, attribute = attribute, nil if attribute.kind_of? String
|
69
|
+
|
70
|
+
options = options.merge(:class => 'control-label')
|
71
|
+
|
72
|
+
case
|
73
|
+
when attribute && text then super(attribute, text, options, &nil)
|
74
|
+
when attribute then super(attribute, nil, options, &nil)
|
75
|
+
when text then template.label_tag(nil, text, options, &nil)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Generates a controls div element for the given +attribute+.
|
81
|
+
#
|
82
|
+
def controls(attribute, text = '', options = {}, &block)
|
83
|
+
text, attribute = attribute, nil if attribute.kind_of? String
|
84
|
+
|
85
|
+
template.content_tag(:div, :class => 'controls') {
|
86
|
+
template.fields_for(
|
87
|
+
self.object_name,
|
88
|
+
self.object,
|
89
|
+
self.options.merge(:builder => TwitterBootstrapFormalwear::FormControls),
|
90
|
+
&block
|
91
|
+
)
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Renders a button with default classes to style it as a form button.
|
97
|
+
#
|
98
|
+
def button(value = nil, options = {})
|
99
|
+
super value, {
|
100
|
+
:type => 'button',
|
101
|
+
:class => 'btn',
|
102
|
+
}.merge(options)
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Renders a submit tag with default classes to style it as a primary form
|
107
|
+
# button.
|
108
|
+
#
|
109
|
+
def submit(value = nil, options = {})
|
110
|
+
self.button value, {
|
111
|
+
:type => 'submit',
|
112
|
+
:class => 'btn btn-primary',
|
113
|
+
}.merge(options)
|
114
|
+
end
|
115
|
+
|
116
|
+
INPUTS.each do |input|
|
117
|
+
define_method input.to_s + '_group' do |attribute, *args, &block|
|
118
|
+
options = args.extract_options!
|
119
|
+
text = args.any? ? args.shift : ''
|
120
|
+
|
121
|
+
self.group(attribute, text, options) do |builder|
|
122
|
+
builder.send(input, attribute, *(args << options), &block)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
define_method input.to_s + '_label' do |attribute, *args, &block|
|
127
|
+
options = args.extract_options!
|
128
|
+
text = args.any? ? args.shift : ''
|
129
|
+
|
130
|
+
self.label(attribute, text, options) do |builder|
|
131
|
+
builder.send(input, attribute, *(args << options), &block)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
define_method input.to_s + '_controls' do |attribute, *args, &block|
|
136
|
+
options = args.extract_options!
|
137
|
+
text = args.any? ? args.shift : ''
|
138
|
+
|
139
|
+
self.controls(attribute, text, options) do |builder|
|
140
|
+
builder.send(input, attribute, *(args << options), &block)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
protected
|
146
|
+
|
147
|
+
def errors_on?(attribute)
|
148
|
+
self.object.errors[attribute].present? if self.object.respond_to?(:errors)
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
#
|
154
|
+
# Returns an HTML id to uniquely identify the markup around an input field.
|
155
|
+
#
|
156
|
+
def _wrapper_id(attribute, suffix = nil)
|
157
|
+
[
|
158
|
+
_object_name + _object_index,
|
159
|
+
_attribute_name(attribute),
|
160
|
+
suffix,
|
161
|
+
].compact.join('_') if attribute
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Returns any classes necessary for the wrapper div around fields for
|
166
|
+
# +attribute+, such as 'errors' if any errors are present on the attribute.
|
167
|
+
# Merges any +classes+ passed in.
|
168
|
+
#
|
169
|
+
def _wrapper_classes(attribute, *classes)
|
170
|
+
classes.push 'error' if attribute and self.errors_on?(attribute)
|
171
|
+
classes.compact.join(' ')
|
172
|
+
end
|
173
|
+
|
174
|
+
def _attribute_name(attribute)
|
175
|
+
attribute.to_s.gsub(/[\?\/\-]$/, '')
|
176
|
+
end
|
177
|
+
|
178
|
+
def _object_name
|
179
|
+
self.object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
180
|
+
end
|
181
|
+
|
182
|
+
def _object_index
|
183
|
+
case
|
184
|
+
when options.has_key?(:index) then options[:index]
|
185
|
+
when defined?(@auto_index) then @auto_index
|
186
|
+
else nil
|
187
|
+
end.to_s
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class TwitterBootstrapFormalwear::FormControls < ActionView::Helpers::FormBuilder
|
192
|
+
attr_reader :template
|
193
|
+
attr_reader :object
|
194
|
+
attr_reader :object_name
|
195
|
+
|
196
|
+
TwitterBootstrapFormalwear::FormBuilder::INPUTS.each do |input|
|
197
|
+
define_method input do |attribute, *args, &block|
|
198
|
+
options = args.extract_options!
|
199
|
+
add_on = options.delete(:add_on)
|
200
|
+
tag = add_on.present? ? :div : :span
|
201
|
+
classes = [ "input", add_on ].compact.join('-')
|
202
|
+
|
203
|
+
template.content_tag(tag, :class => classes) do
|
204
|
+
template.concat super attribute, *(args << options)
|
205
|
+
template.concat self.error_span(attribute) if self.errors_on?(attribute)
|
206
|
+
block.call if block.present?
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
=begin
|
212
|
+
def check_box(attribute, text, options = {}, checked_value = 1, unchecked_value = 0)
|
213
|
+
klasses = _merge_classes 'checkbox', options.delete(:inline) && 'inline'
|
214
|
+
|
215
|
+
self.label(attribute, :class => klasses) do
|
216
|
+
template.concat super(attribute, options, checked_value, unchecked_value)
|
217
|
+
template.concat text
|
218
|
+
yield if block_given?
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def radio_button(attribute, value, text = nil, options = {})
|
223
|
+
klasses = _merge_classes 'radio', options.delete(:inline) && 'inline'
|
224
|
+
|
225
|
+
self.label(attribute, :class => klasses) do
|
226
|
+
template.concat super(attribute, value, options)
|
227
|
+
template.concat text || value.to_s.humanize.titleize
|
228
|
+
yield if block_given?
|
229
|
+
end
|
230
|
+
end
|
231
|
+
=end
|
232
|
+
|
233
|
+
protected
|
234
|
+
|
235
|
+
def error_span(attribute, options = {})
|
236
|
+
options[:class] = _merge_classes options[:class], 'help-inline'
|
237
|
+
|
238
|
+
template.content_tag :span,
|
239
|
+
self.errors_for(attribute),
|
240
|
+
:class => options[:class]
|
241
|
+
end
|
242
|
+
|
243
|
+
def errors_for(attribute)
|
244
|
+
self.object.errors[attribute].try(:join, ', ')
|
245
|
+
end
|
246
|
+
|
247
|
+
def errors_on?(attribute)
|
248
|
+
self.object.errors[attribute].present? if self.object.respond_to?(:errors)
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def _merge_classes(string, *classes)
|
254
|
+
string.to_s.split(' ').push(*classes.compact).join(' ')
|
255
|
+
end
|
256
|
+
end
|