thorero-helpers 0.5.0.11 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README +15 -3
- data/Rakefile +18 -13
- data/TODO +5 -0
- data/lib/merb_helpers.rb +39 -0
- data/lib/merb_helpers/core_ext.rb +31 -0
- data/lib/merb_helpers/date_time_helpers.rb +202 -0
- data/lib/merb_helpers/form_helpers.rb +655 -0
- data/lib/merb_helpers/ordinalize.rb +54 -0
- data/lib/merb_helpers/tag_helpers.rb +61 -0
- data/lib/merb_helpers/time_dsl.rb +59 -0
- metadata +13 -8
- data/lib/merb-laszlo.rb +0 -38
- data/lib/merb-laszlo/controllers.rb +0 -53
- data/lib/merb-laszlo/helpers.rb +0 -45
- data/lib/merb-laszlo/merbtasks.rb +0 -6
data/LICENSE
CHANGED
data/README
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
merb_helpers
|
2
|
+
=================
|
3
3
|
|
4
|
-
A plugin for the Merb framework that provides
|
4
|
+
A plugin for the Merb Web framework that provides different view helpers.
|
5
|
+
|
6
|
+
To use this plugin in merb in your app
|
7
|
+
|
8
|
+
config/dependencies.rb
|
9
|
+
|
10
|
+
#...
|
11
|
+
|
12
|
+
dependency "merb_helpers"
|
13
|
+
|
14
|
+
#...
|
15
|
+
|
16
|
+
# TODO: describe date_time_helpers, form_helpers, tag_helpers
|
data/Rakefile
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'rubygems/specification'
|
3
|
-
require 'date'
|
4
2
|
require 'rake/gempackagetask'
|
3
|
+
require "rake/rdoctask"
|
5
4
|
require "extlib"
|
6
5
|
require 'merb-core/tasks/merb_rake_helper'
|
7
6
|
require "spec/rake/spectask"
|
@@ -11,7 +10,7 @@ require "spec/rake/spectask"
|
|
11
10
|
##############################################################################
|
12
11
|
RUBY_FORGE_PROJECT = "thorero"
|
13
12
|
PROJECT_URL = "http://merbivore.com"
|
14
|
-
PROJECT_SUMMARY = "
|
13
|
+
PROJECT_SUMMARY = "Helper support for merb (similar to the Rails form helpers)"
|
15
14
|
PROJECT_DESCRIPTION = PROJECT_SUMMARY
|
16
15
|
|
17
16
|
GEM_AUTHOR = "Yehuda Katz"
|
@@ -19,7 +18,7 @@ GEM_EMAIL = "ykatz@engineyard.com"
|
|
19
18
|
|
20
19
|
GEM_NAME = "thorero-helpers"
|
21
20
|
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
22
|
-
GEM_VERSION = "0.
|
21
|
+
GEM_VERSION = (Merb::MORE_VERSION rescue "0.9.4") + PKG_BUILD
|
23
22
|
|
24
23
|
RELEASE_NAME = "REL #{GEM_VERSION}"
|
25
24
|
|
@@ -31,7 +30,7 @@ spec = Gem::Specification.new do |s|
|
|
31
30
|
s.version = GEM_VERSION
|
32
31
|
s.platform = Gem::Platform::RUBY
|
33
32
|
s.has_rdoc = true
|
34
|
-
s.extra_rdoc_files = ["README", "LICENSE"]
|
33
|
+
s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
|
35
34
|
s.summary = PROJECT_SUMMARY
|
36
35
|
s.description = PROJECT_DESCRIPTION
|
37
36
|
s.author = GEM_AUTHOR
|
@@ -39,20 +38,13 @@ spec = Gem::Specification.new do |s|
|
|
39
38
|
s.homepage = PROJECT_URL
|
40
39
|
s.add_dependency('merb-core', '>= 0.9.4')
|
41
40
|
s.require_path = 'lib'
|
42
|
-
s.files = %w(LICENSE README Rakefile) + Dir.glob("{lib,specs}/**/*")
|
41
|
+
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,specs}/**/*")
|
43
42
|
end
|
44
43
|
|
45
44
|
Rake::GemPackageTask.new(spec) do |pkg|
|
46
45
|
pkg.gem_spec = spec
|
47
46
|
end
|
48
47
|
|
49
|
-
desc "create a gemspec file"
|
50
|
-
task :make_spec do
|
51
|
-
File.open("#{NAME}.gemspec", "w") do |file|
|
52
|
-
file.puts spec.to_ruby
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
48
|
##############################################################################
|
57
49
|
# Installation
|
58
50
|
##############################################################################
|
@@ -85,3 +77,16 @@ Spec::Rake::SpecTask.new('rcov') do |t|
|
|
85
77
|
t.rcov_dir = 'coverage'
|
86
78
|
t.rcov_opts = ['--exclude', 'gems', '--exclude', 'spec']
|
87
79
|
end
|
80
|
+
|
81
|
+
##############################################################################
|
82
|
+
# Documentation
|
83
|
+
##############################################################################
|
84
|
+
Rake::RDocTask.new do |rdoc|
|
85
|
+
files = ['README', 'LICENSE',
|
86
|
+
'lib/**/*.rb']
|
87
|
+
rdoc.rdoc_files.add(files)
|
88
|
+
rdoc.main = 'README'
|
89
|
+
rdoc.title = 'Merb Helper Docs'
|
90
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
91
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
92
|
+
end
|
data/TODO
ADDED
data/lib/merb_helpers.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Merb
|
2
|
+
|
3
|
+
module Helpers
|
4
|
+
|
5
|
+
@@helpers_dir = File.dirname(__FILE__) / 'merb_helpers'
|
6
|
+
@@helpers_files = Dir["#{@@helpers_dir}/*_helpers.rb"].collect {|h| h.match(/\/(\w+)\.rb/)[1]}
|
7
|
+
|
8
|
+
def self.load
|
9
|
+
require @@helpers_dir + '/time_dsl'
|
10
|
+
require @@helpers_dir + '/ordinalize'
|
11
|
+
require @@helpers_dir + '/core_ext'
|
12
|
+
|
13
|
+
if Merb::Plugins.config[:merb_helpers]
|
14
|
+
config = Merb::Plugins.config[:merb_helpers]
|
15
|
+
raise "With and Without options have been deprecated" if config[:with] || config[:without]
|
16
|
+
|
17
|
+
if config[:include] && !config[:include].empty?
|
18
|
+
load_helpers(config[:include])
|
19
|
+
else
|
20
|
+
# This is in case someone defines an entry in the config,
|
21
|
+
# but doesn't put in a with or without option
|
22
|
+
load_helpers
|
23
|
+
end
|
24
|
+
|
25
|
+
else
|
26
|
+
load_helpers
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Load only specific helpers instead of loading all the helpers
|
31
|
+
def self.load_helpers(helpers = @@helpers_files)
|
32
|
+
helpers.each {|helper| Kernel.load(File.join(@@helpers_dir, "#{helper}.rb") )} # using load here allows specs to work
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
Merb::Helpers.load
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Date
|
2
|
+
include OrdinalizedFormatting
|
3
|
+
|
4
|
+
# Converts a Date instance to a Time, where the time is set to the beginning of the day.
|
5
|
+
# The timezone can be either :local or :utc (default :utc).
|
6
|
+
#
|
7
|
+
# ==== Examples:
|
8
|
+
# date = Date.new(2007, 11, 10)
|
9
|
+
# date.to_s # => 2007-11-10
|
10
|
+
#
|
11
|
+
# date.to_time # => Sat Nov 10 00:00:00 UTC 2007
|
12
|
+
# date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007
|
13
|
+
# date.to_time(:local) # => Sat Nov 10 00:00:00 -0800 2007
|
14
|
+
#
|
15
|
+
def to_time(form = :utc)
|
16
|
+
::Time.send("#{form}", year, month, day)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_date; self; end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Time
|
23
|
+
include OrdinalizedFormatting
|
24
|
+
|
25
|
+
# Ruby 1.8-cvs and 1.9 define private Time#to_date
|
26
|
+
%w(to_date to_datetime).each do |method|
|
27
|
+
public method if private_instance_methods.include?(method)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_time; self; end
|
31
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
module Merb
|
2
|
+
module Helpers
|
3
|
+
# Provides a number of methods for displaying and dealing with dates and times
|
4
|
+
#
|
5
|
+
# Parts were strongly based on http://ar-code.svn.engineyard.com/plugins/relative_time_helpers/, and
|
6
|
+
# active_support
|
7
|
+
#
|
8
|
+
# The key methods are `relative_date`, `relative_date_span`, and `relative_time_span`. This also gives
|
9
|
+
# you the Rails style Time DSL for working with numbers eg. 3.months.ago or 5.days.until(1.year.from_now)
|
10
|
+
module DateAndTime
|
11
|
+
@@time_class = Time
|
12
|
+
@@time_output = {
|
13
|
+
:today => 'today',
|
14
|
+
:yesterday => 'yesterday',
|
15
|
+
:tomorrow => 'tomorrow',
|
16
|
+
:initial_format => '%b %d',
|
17
|
+
:year_format => ', %Y'
|
18
|
+
}
|
19
|
+
@@date_formats = {
|
20
|
+
:db => "%Y-%m-%d %H:%M:%S",
|
21
|
+
:time => "%H:%M",
|
22
|
+
:short => "%d %b %H:%M",
|
23
|
+
:long => "%B %d, %Y %H:%M",
|
24
|
+
:long_ordinal => lambda { |time| time.strftime("%B #{time.day.ordinalize}, %Y %H:%M") },
|
25
|
+
:rfc822 => "%a, %d %b %Y %H:%M:%S %z"
|
26
|
+
}
|
27
|
+
|
28
|
+
def self.time_class
|
29
|
+
@@time_class
|
30
|
+
end
|
31
|
+
|
32
|
+
# ==== Parameters
|
33
|
+
# format<Symbol>:: time format to use
|
34
|
+
# locale<String, Symbol>:: An optional value which can be used by localization plugins
|
35
|
+
#
|
36
|
+
# ==== Returns
|
37
|
+
# String:: a string used to format time using #strftime
|
38
|
+
def self.time_output(format, locale=nil)
|
39
|
+
@@time_output[format]
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.date_formats
|
43
|
+
@@date_formats
|
44
|
+
end
|
45
|
+
|
46
|
+
# Gives you a relative date in an attractive format
|
47
|
+
#
|
48
|
+
# ==== Parameters
|
49
|
+
# time<~to_date>:: The Date or Time to test
|
50
|
+
# locale<String, Symbol>:: An optional value which can be used by localization plugins
|
51
|
+
#
|
52
|
+
# ==== Returns
|
53
|
+
# String:: Relative date
|
54
|
+
#
|
55
|
+
# ==== Examples
|
56
|
+
# relative_date(Time.now.utc) => "today"
|
57
|
+
# relative_date(5.days.ago) => "March 5th"
|
58
|
+
# relative_date(1.year.ago) => "March 10th, 2007"
|
59
|
+
def relative_date(time, locale=nil)
|
60
|
+
date = time.to_date
|
61
|
+
today = DateAndTime.time_class.now.to_date
|
62
|
+
if date == today
|
63
|
+
DateAndTime.time_output(:today, locale)
|
64
|
+
elsif date == (today - 1)
|
65
|
+
DateAndTime.time_output(:yesterday, locale)
|
66
|
+
elsif date == (today + 1)
|
67
|
+
DateAndTime.time_output(:tomorrow, locale)
|
68
|
+
else
|
69
|
+
fmt = DateAndTime.time_output(:initial_format, locale).dup
|
70
|
+
fmt << DateAndTime.time_output(:year_format, locale) unless date.year == today.year
|
71
|
+
time.strftime_ordinalized(fmt, locale)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Gives you a relative date span in an attractive format
|
76
|
+
#
|
77
|
+
# ==== Parameters
|
78
|
+
# times<~first,~last>:: The Dates or Times to test
|
79
|
+
#
|
80
|
+
# ==== Returns
|
81
|
+
# String:: The sexy relative date span
|
82
|
+
#
|
83
|
+
# ==== Examples
|
84
|
+
# relative_date([1.second.ago, 10.seconds.ago]) => "March 10th"
|
85
|
+
# relative_date([1.year.ago, 1.year.ago) => "March 10th, 2007"
|
86
|
+
# relative_date([Time.now, 1.day.from_now]) => "March 10th - 11th"
|
87
|
+
# relative_date([Time.now, 1.year.ago]) => "March 10th, 2007 - March 10th, 2008"
|
88
|
+
def relative_date_span(times)
|
89
|
+
times = [times.first, times.last].collect! { |t| t.to_date }
|
90
|
+
times.sort!
|
91
|
+
if times.first == times.last
|
92
|
+
relative_date(times.first)
|
93
|
+
else
|
94
|
+
first = times.first; last = times.last; now = DateAndTime.time_class.now
|
95
|
+
arr = [first.strftime_ordinalized('%b %d')]
|
96
|
+
arr << ", #{first.year}" unless first.year == last.year
|
97
|
+
arr << ' - '
|
98
|
+
arr << last.strftime('%b') << ' ' unless first.year == last.year && first.month == last.month
|
99
|
+
arr << last.day.ordinalize
|
100
|
+
arr << ", #{last.year}" unless first.year == last.year && last.year == now.year
|
101
|
+
arr.to_s
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Gives you a relative date span in an attractive format
|
106
|
+
#
|
107
|
+
# ==== Parameters
|
108
|
+
# times<~first,~last>:: The Dates or Times to test
|
109
|
+
#
|
110
|
+
# ==== Returns
|
111
|
+
# String:: The sexy relative time span
|
112
|
+
#
|
113
|
+
# ==== Examples
|
114
|
+
# relative_time_span([1.second.ago, 10.seconds.ago]) => "12:00 - 12:09 AM March 10th"
|
115
|
+
# relative_time_span([1.year.ago, 1.year.ago) => "12:09 AM March 10th, 2007"
|
116
|
+
# relative_time_span([Time.now, 13.hours.from_now]) => "12:09 AM - 1:09 PM March 10th"
|
117
|
+
# relative_time_span([Time.now, 1.year.ago]) => "12:09 AM March 10th, 2007 - 12:09 AM March 10th, 2008"
|
118
|
+
def relative_time_span(times)
|
119
|
+
times = [times.first, times.last].collect! { |t| t.to_time }
|
120
|
+
times.sort!
|
121
|
+
if times.first == times.last
|
122
|
+
"#{prettier_time(times.first)} #{relative_date(times.first)}"
|
123
|
+
elsif times.first.to_date == times.last.to_date
|
124
|
+
same_half = (times.first.hour/12 == times.last.hour/12)
|
125
|
+
"#{prettier_time(times.first, !same_half)} - #{prettier_time(times.last)} #{relative_date(times.first)}"
|
126
|
+
|
127
|
+
else
|
128
|
+
first = times.first; last = times.last; now = DateAndTime.time_class.now
|
129
|
+
arr = [prettier_time(first)]
|
130
|
+
arr << ' '
|
131
|
+
arr << first.strftime_ordinalized('%b %d')
|
132
|
+
arr << ", #{first.year}" unless first.year == last.year
|
133
|
+
arr << ' - '
|
134
|
+
arr << prettier_time(last)
|
135
|
+
arr << ' '
|
136
|
+
arr << last.strftime('%b') << ' ' unless first.year == last.year && first.month == last.month
|
137
|
+
arr << last.day.ordinalize
|
138
|
+
arr << ", #{last.year}" unless first.year == last.year && last.year == now.year
|
139
|
+
arr.to_s
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Condenses time... very similar to time_ago_in_words in ActionPack
|
144
|
+
#
|
145
|
+
# ==== Parameters
|
146
|
+
# from_time<~to_time>:: The Date or Time to start from
|
147
|
+
# to_time<~to_time>:: The Date or Time to go to, Defaults to Time.now.utc
|
148
|
+
# include_seconds<Boolean>:: Count the seconds initially, Defaults to false
|
149
|
+
# locale<String, Symbol>:: An optional value which can be used by localization plugins
|
150
|
+
#
|
151
|
+
# ==== Returns
|
152
|
+
# String:: The time distance
|
153
|
+
#
|
154
|
+
# ==== Examples
|
155
|
+
# time_lost_in_words(3.minutes.from_now) # => 3 minutes
|
156
|
+
# time_lost_in_words(Time.now - 15.hours) # => 15 hours
|
157
|
+
# time_lost_in_words(Time.now, 3.minutes.from_now) # => 3 minutes
|
158
|
+
# time_lost_in_words(Time.now) # => less than a minute
|
159
|
+
# time_lost_in_words(Time.now, Time.now, true) # => less than 5 seconds
|
160
|
+
#
|
161
|
+
def time_lost_in_words(from_time, to_time = Time.now.utc, include_seconds = false, locale=nil)
|
162
|
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
163
|
+
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
164
|
+
distance_in_minutes = (((to_time - from_time).abs)/60).round
|
165
|
+
distance_in_seconds = ((to_time - from_time).abs).round
|
166
|
+
|
167
|
+
case distance_in_minutes
|
168
|
+
when 0..1
|
169
|
+
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
|
170
|
+
case distance_in_seconds
|
171
|
+
when 0..4 then 'less than 5 seconds'
|
172
|
+
when 5..9 then 'less than 10 seconds'
|
173
|
+
when 10..19 then 'less than 20 seconds'
|
174
|
+
when 20..39 then 'half a minute'
|
175
|
+
when 40..59 then 'less than a minute'
|
176
|
+
else '1 minute'
|
177
|
+
end
|
178
|
+
|
179
|
+
when 2..44 then "#{distance_in_minutes} minutes"
|
180
|
+
when 45..89 then 'about 1 hour'
|
181
|
+
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
182
|
+
when 1440..2879 then '1 day'
|
183
|
+
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
184
|
+
when 43200..86399 then 'about 1 month'
|
185
|
+
when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
|
186
|
+
when 525600..1051199 then 'about 1 year'
|
187
|
+
else "over #{(distance_in_minutes / 525600).round} years"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
alias :time_ago_in_words :time_lost_in_words
|
191
|
+
|
192
|
+
def prettier_time(time, ampm=true, locale=nil)
|
193
|
+
time.strftime("%I:%M#{" %p" if ampm}").sub(/^0/, '')
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class Merb::Controller
|
200
|
+
include Merb::Helpers::DateAndTime
|
201
|
+
end
|
202
|
+
|
@@ -0,0 +1,655 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/tag_helpers"
|
2
|
+
|
3
|
+
module Merb
|
4
|
+
|
5
|
+
# Merb helpers include several helpers used for simplifying view creation.
|
6
|
+
# The available helpers currently include form tag helpers for both resource based and generic HTML form tag creation
|
7
|
+
module Helpers
|
8
|
+
# Provides a number of methods for creating form tags which may be used either with or without the presence of ORM specific models.
|
9
|
+
# There are two types of form helpers: those that specifically work with model attributes and those that don't.
|
10
|
+
# This helper deals with both model attributes and generic form tags. Model attributes generally end in "_control" such as +text_control+,
|
11
|
+
# and generic tags end with "_field", such as +text_field+
|
12
|
+
#
|
13
|
+
# The core method of this helper, +form_for+, gives you the ability to create a form for a resource.
|
14
|
+
# For example, let's say that you have a model <tt>Person</tt> and want to create a new instance of it:
|
15
|
+
#
|
16
|
+
# <% form_for :person, :action => url(:people) do %>
|
17
|
+
# <%= text_control :first_name, :label => 'First Name' %>
|
18
|
+
# <%= text_control :last_name, :label => 'Last Name' %>
|
19
|
+
# <%= submit_button 'Create' %>
|
20
|
+
# <% end %>
|
21
|
+
#
|
22
|
+
# The HTML generated for this would be:
|
23
|
+
#
|
24
|
+
# <form action="/people/create" method="post">
|
25
|
+
# <label for="person_first_name">First Name</label>
|
26
|
+
# <input id="person_first_name" name="person[first_name]" size="30" type="text" />
|
27
|
+
# <label for="person_last_name">Last Name</label>
|
28
|
+
# <input id="person_last_name" name="person[last_name]" size="30" type="text" />
|
29
|
+
# <button type="submit">Create</button>
|
30
|
+
# </form>
|
31
|
+
#
|
32
|
+
# You may also create a normal form using form_tag
|
33
|
+
# <% form_tag(:action => url(:controller => "foo", :action => "bar", :id => 1)) do %>
|
34
|
+
# <%= text_field :name => 'first_name', :label => 'First Name' %>
|
35
|
+
# <%= submit_button 'Create' %>
|
36
|
+
# <% end %>
|
37
|
+
#
|
38
|
+
# The HTML generated for this would be:
|
39
|
+
#
|
40
|
+
# <form action="/foo/bar/1" method="post">
|
41
|
+
# <label for="first_name">First Name</label><input id="first_name" name="first_name" size="30" type="text" />
|
42
|
+
# <button type="submit">Create</button>
|
43
|
+
# </form>
|
44
|
+
module Form
|
45
|
+
|
46
|
+
# Provides a HTML formatted display of resource errors in an unordered list with a h2 form submission error
|
47
|
+
# ==== Options
|
48
|
+
# +build_li+:: Block for generating a list item for an error. It receives an instance of the error.
|
49
|
+
# +html_class+:: Set for custom error div class default is <tt>submission_failed<tt>
|
50
|
+
#
|
51
|
+
# ==== Examples
|
52
|
+
# <%= error_messages_for :person %>
|
53
|
+
# <%= error_messages_for :person {|errors| "You can has probs nao: #{errors.size} of em!"}
|
54
|
+
# <%= error_messages_for :person, lambda{|error| "<li class='aieeee'>#{error.join(' ')}"} %>
|
55
|
+
# <%= error_messages_for :person, nil, 'bad_mojo' %>
|
56
|
+
def error_messages_for(obj, build_li = nil, html_class='error')
|
57
|
+
obj = self.instance_variable_get("@#{obj}") if obj.kind_of?(Symbol)
|
58
|
+
|
59
|
+
return "" unless obj.respond_to?(:errors) && ! obj.errors.empty?
|
60
|
+
|
61
|
+
if obj.errors.respond_to?(:each) # AR & DM
|
62
|
+
build_li ||= lambda{|err| "<li>#{err.join(' ')}</li>"}
|
63
|
+
error_collection = obj.errors
|
64
|
+
else # Sequel
|
65
|
+
build_li ||= lambda{|msg| "<li>#{msg}</li>"}
|
66
|
+
error_collection = obj.errors.full_messages
|
67
|
+
end
|
68
|
+
error_count = error_collection.size
|
69
|
+
|
70
|
+
header_message = if block_given?
|
71
|
+
yield(obj.errors)
|
72
|
+
else
|
73
|
+
error_plurality = (error_count == 1 ? 'problem' : 'problems')
|
74
|
+
"<h2>Form submission failed because of #{error_count} #{error_plurality}</h2>"
|
75
|
+
end
|
76
|
+
|
77
|
+
markup = %Q{
|
78
|
+
<div class='#{html_class}'>
|
79
|
+
#{header_message}
|
80
|
+
<ul>
|
81
|
+
}
|
82
|
+
|
83
|
+
error_collection.each {|error, message| markup << build_li.call([error, message])}
|
84
|
+
|
85
|
+
markup << %Q{
|
86
|
+
</ul>
|
87
|
+
</div>
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def obj_from_ivar_or_sym(obj)
|
92
|
+
obj.is_a?(Symbol) ? instance_variable_get("@#{obj}") : obj
|
93
|
+
end
|
94
|
+
|
95
|
+
# Generates a form tag, which accepts a block that is not directly based on resource attributes
|
96
|
+
#
|
97
|
+
# <% form_tag(:action => url(:controller => "foo", :action => "bar", :id => 1)) do %>
|
98
|
+
# <%= text_field :name => 'first_name', :label => 'First Name' %>
|
99
|
+
# <%= submit_button 'Create' %>
|
100
|
+
# <% end %>
|
101
|
+
#
|
102
|
+
# The HTML generated for this would be:
|
103
|
+
#
|
104
|
+
# <form action="/foo/bar/1" method="post">
|
105
|
+
# <label for="first_name">First Name</label><input id="first_name" name="first_name" size="30" type="text" />
|
106
|
+
# <input name="commit" type="submit" value="Create" />
|
107
|
+
# </form>
|
108
|
+
def form_tag(attrs = {}, &block)
|
109
|
+
set_multipart_attribute!(attrs)
|
110
|
+
fake_form_method = set_form_method(attrs)
|
111
|
+
concat(open_tag("form", attrs), block.binding)
|
112
|
+
concat(generate_fake_form_method(fake_form_method), block.binding) if fake_form_method
|
113
|
+
concat(capture(&block), block.binding)
|
114
|
+
concat("</form>", block.binding)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Generates a resource specific form tag which accepts a block, this also provides automatic resource routing.
|
118
|
+
# <% form_for :person, :action => url(:people) do %>
|
119
|
+
# <%= text_control :first_name, :label => 'First Name' %>
|
120
|
+
# <%= text_control :last_name, :label => 'Last Name' %>
|
121
|
+
# <%= submit_button 'Create' %>
|
122
|
+
# <% end %>
|
123
|
+
#
|
124
|
+
# The HTML generated for this would be:
|
125
|
+
#
|
126
|
+
# <form action="/people/create" method="post">
|
127
|
+
# <label for="person[first_name]">First Name</label><input id="person_first_name" name="person[first_name]" size="30" type="text" />
|
128
|
+
# <label for="person[last_name]">Last Name</label><input id="person_last_name" name="person[last_name]" size="30" type="text" />
|
129
|
+
# <input name="commit" type="submit" value="Create" />
|
130
|
+
# </form>
|
131
|
+
def form_for(obj, attrs={}, &block)
|
132
|
+
set_multipart_attribute!(attrs)
|
133
|
+
obj = obj_from_ivar_or_sym(obj)
|
134
|
+
fake_form_method = set_form_method(attrs, obj)
|
135
|
+
concat(open_tag("form", attrs), block.binding)
|
136
|
+
concat(generate_fake_form_method(fake_form_method), block.binding) if fake_form_method
|
137
|
+
fields_for(obj, attrs, &block)
|
138
|
+
concat("</form>", block.binding)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Creates a scope around a specific resource object like form_for, but doesnt create the form tags themselves.
|
142
|
+
# This makes fields_for suitable for specifying additional resource objects in the same form.
|
143
|
+
#
|
144
|
+
# ==== Examples
|
145
|
+
# <% form_for :person, :action => url(:people) do %>
|
146
|
+
# <%= text_control :first_name, :label => 'First Name' %>
|
147
|
+
# <%= text_control :last_name, :label => 'Last Name' %>
|
148
|
+
# <% fields_for :permission do %>
|
149
|
+
# <%= checkbox_control :is_admin, :label => 'Administrator' %>
|
150
|
+
# <% end %>
|
151
|
+
# <%= submit_button 'Create' %>
|
152
|
+
# <% end %>
|
153
|
+
def fields_for(obj, attrs=nil, &block)
|
154
|
+
@_obj ||= nil
|
155
|
+
@_block ||= nil
|
156
|
+
@_object_name ||= nil
|
157
|
+
obj = obj_from_ivar_or_sym(obj)
|
158
|
+
old_obj, @_obj = @_obj, obj
|
159
|
+
old_block, @_block = @_block, block
|
160
|
+
old_object_name, @_object_name = @_object_name, "#{@_obj.class}".snake_case
|
161
|
+
|
162
|
+
concat(capture(&block), block.binding)
|
163
|
+
|
164
|
+
@_obj, @_block, @_object_name = old_obj, old_block, old_object_name
|
165
|
+
end
|
166
|
+
|
167
|
+
def control_name(col)
|
168
|
+
"#{@_object_name}[#{col}]"
|
169
|
+
end
|
170
|
+
|
171
|
+
def control_id(col)
|
172
|
+
"#{@_object_name}_#{col}"
|
173
|
+
end
|
174
|
+
|
175
|
+
def control_value(col)
|
176
|
+
escape_xml(@_obj.send(col))
|
177
|
+
end
|
178
|
+
|
179
|
+
def control_name_value(col, attrs)
|
180
|
+
{:name => control_name(col), :value => control_value(col)}.merge(attrs)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Provides a HTML text input tag based on a resource attribute.
|
184
|
+
#
|
185
|
+
# ==== Example
|
186
|
+
# <% form_for :person, :action => url(:people) do %>
|
187
|
+
# <%= text_control :first_name, :label => 'First Name' %>
|
188
|
+
# <% end %>
|
189
|
+
def text_control(col, attrs = {})
|
190
|
+
errorify_field(attrs, col)
|
191
|
+
attrs.merge!(:id => control_id(col))
|
192
|
+
text_field(control_name_value(col, attrs))
|
193
|
+
end
|
194
|
+
|
195
|
+
# Provides a generic HTML text input tag.
|
196
|
+
# Provides a HTML text input tag based on a resource attribute.
|
197
|
+
#
|
198
|
+
# ==== Example
|
199
|
+
# <%= text_field :name => :fav_color, :label => 'Your Favorite Color' %>
|
200
|
+
# # => <label for="fav_color">Your Favorite Color</label><input type="text" name="fav_color" id="fav_color"/>
|
201
|
+
def text_field(attrs = {})
|
202
|
+
attrs.merge!(:type => "text")
|
203
|
+
attrs.add_html_class!("text")
|
204
|
+
optional_label(attrs) { self_closing_tag("input", attrs) }
|
205
|
+
end
|
206
|
+
|
207
|
+
# Provides a HTML password input based on a resource attribute.
|
208
|
+
# This is generally used within a resource block such as +form_for+.
|
209
|
+
#
|
210
|
+
# ==== Example
|
211
|
+
# <%= password_control :password, :label => 'New Password' %>
|
212
|
+
def password_control(col, attrs = {})
|
213
|
+
attrs.merge!(:name => control_name(col), :id => control_id(col))
|
214
|
+
errorify_field(attrs, col)
|
215
|
+
password_field({:name => control_name(col)}.merge(attrs))
|
216
|
+
end
|
217
|
+
|
218
|
+
# Provides a generic HTML password input tag.
|
219
|
+
#
|
220
|
+
# ==== Example
|
221
|
+
# <%= password_field :name => :password, :label => "Password" %>
|
222
|
+
# # => <label for="password">Password</label><input type="password" name="password" id="password"/>
|
223
|
+
def password_field(attrs = {})
|
224
|
+
attrs.merge!(:type => 'password')
|
225
|
+
attrs.add_html_class!("password")
|
226
|
+
optional_label(attrs) { self_closing_tag("input", attrs) }
|
227
|
+
end
|
228
|
+
|
229
|
+
# translate column values from the db to boolean
|
230
|
+
# nil, false, 0 and '0' are false. All others are true
|
231
|
+
def col_val_to_bool(val)
|
232
|
+
!(val == "0" || val == 0 || !val)
|
233
|
+
end
|
234
|
+
private :col_val_to_bool
|
235
|
+
|
236
|
+
# Provides a HTML checkbox input based on a resource attribute.
|
237
|
+
# This is generally used within a resource block such as +form_for+.
|
238
|
+
#
|
239
|
+
# ==== Example
|
240
|
+
# <%= checkbox_control :is_activated, :label => "Activated?" %>
|
241
|
+
def checkbox_control(col, attrs = {}, hidden_attrs={})
|
242
|
+
errorify_field(attrs, col)
|
243
|
+
method_name = @_obj.respond_to?(col) ? col : :"#{col}?"
|
244
|
+
attrs.merge!(:checked => "checked") if col_val_to_bool(@_obj.send(method_name))
|
245
|
+
attrs.merge!(:id => control_id(col))
|
246
|
+
attrs = {:name => control_name(col), :value => control_value(method_name)}.merge(attrs)
|
247
|
+
checkbox_field(attrs, hidden_attrs)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Provides a generic HTML checkbox input tag.
|
251
|
+
# There are two ways this tag can be generated, based on the
|
252
|
+
# option :boolean. If not set to true, a "magic" input is generated.
|
253
|
+
# Otherwise, an input is created that can be easily used for passing
|
254
|
+
# an array of values to the application.
|
255
|
+
#
|
256
|
+
# ==== Example
|
257
|
+
# <% checkbox_field :name => "is_activated", :value => "1" %>
|
258
|
+
#
|
259
|
+
# <% checkbox_field :name => "choices[]", :boolean => false, :value => "dog" %>
|
260
|
+
# <% checkbox_field :name => "choices[]", :boolean => false, :value => "cat" %>
|
261
|
+
# <% checkbox_field :name => "choices[]", :boolean => false, :value => "weasle" %>
|
262
|
+
def checkbox_field(attrs = {}, hidden_attrs={})
|
263
|
+
boolbox = true
|
264
|
+
boolbox = false if ( attrs.has_key?(:boolean) and !attrs[:boolean] )
|
265
|
+
attrs.delete(:boolean)
|
266
|
+
|
267
|
+
if( boolbox )
|
268
|
+
on = attrs.delete(:on) || 1
|
269
|
+
off = attrs.delete(:off) || 0
|
270
|
+
attrs[:value] = on if ( (v = attrs[:value]).nil? || v != "" )
|
271
|
+
else
|
272
|
+
# HTML-escape the value attribute
|
273
|
+
attrs[:value] = escape_xml( attrs[:value] )
|
274
|
+
end
|
275
|
+
|
276
|
+
attrs.merge!(:type => :checkbox)
|
277
|
+
attrs.add_html_class!("checkbox")
|
278
|
+
(boolbox ? hidden_field({:name => attrs[:name], :value => off}.merge(hidden_attrs)) : '') + optional_label(attrs){self_closing_tag("input", attrs)}
|
279
|
+
end
|
280
|
+
|
281
|
+
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +col+) on an object
|
282
|
+
# resource within a +form_for+ resource block. Additional options on the input tag can be passed as a
|
283
|
+
# hash with +attrs+. These options will be tagged onto the HTML as an HTML element attribute as in the example
|
284
|
+
# shown.
|
285
|
+
#
|
286
|
+
# ==== Example
|
287
|
+
# <%= hidden_control :identifier %>
|
288
|
+
# # => <input id="person_identifier" name="person[identifier]" type="hidden" value="#{@person.identifier}" />
|
289
|
+
def hidden_control(col, attrs = {})
|
290
|
+
attrs.delete(:label)
|
291
|
+
errorify_field(attrs, col)
|
292
|
+
attrs[:class] ||= "hidden"
|
293
|
+
hidden_field(control_name_value(col, attrs))
|
294
|
+
end
|
295
|
+
|
296
|
+
# Provides a generic HTML hidden input field.
|
297
|
+
#
|
298
|
+
# ==== Example
|
299
|
+
# <%= hidden_field :name => "secret", :value => "some secret value" %>
|
300
|
+
def hidden_field(attrs = {})
|
301
|
+
attrs.delete(:label)
|
302
|
+
attrs.merge!(:type => :hidden)
|
303
|
+
attrs.add_html_class!("hidden")
|
304
|
+
self_closing_tag("input", attrs)
|
305
|
+
end
|
306
|
+
|
307
|
+
# Provides a HTML radio input tag based on a resource attribute.
|
308
|
+
#
|
309
|
+
# ==== Example
|
310
|
+
# <% form_for :person, :action => url(:people) do %>
|
311
|
+
# <%= radio_control :first_name %>
|
312
|
+
# <% end %>
|
313
|
+
def radio_control(col, attrs = {})
|
314
|
+
errorify_field(attrs, col)
|
315
|
+
attrs.merge!(:id => control_id(col))
|
316
|
+
val = @_obj.send(col)
|
317
|
+
attrs.merge!(:checked => "checked") if val.to_s == attrs[:value]
|
318
|
+
optional_label(attrs) { radio_field(control_name_value(col, attrs)) }
|
319
|
+
end
|
320
|
+
|
321
|
+
# Provides a radio group based on a resource attribute.
|
322
|
+
# This is generally used within a resource block such as +form_for+.
|
323
|
+
#
|
324
|
+
# ==== Examples
|
325
|
+
# <%# the labels are the options %>
|
326
|
+
# <%= radio_group_control :my_choice, [5,6,7] %>
|
327
|
+
#
|
328
|
+
# <%# custom labels %>
|
329
|
+
# <%= radio_group_control :my_choice, [{:value => 5, :label => "five"}] %>
|
330
|
+
def radio_group_control(col, options = [], attrs = {})
|
331
|
+
errorify_field(attrs, col)
|
332
|
+
val = @_obj.send(col)
|
333
|
+
ret = ""
|
334
|
+
options.each do |opt|
|
335
|
+
value, label = opt.is_a?(Hash) ? [opt.delete(:value), opt.delete(:label)] : [opt, opt]
|
336
|
+
hash = {:name => "#{@_object_name}[#{col}]", :value => value, :label => label, :id => "#{control_id(col)}_#{value}" }
|
337
|
+
hash.merge!(opt) if opt.is_a?(Hash)
|
338
|
+
hash.merge!(:checked => "checked") if val.to_s == value.to_s
|
339
|
+
ret << radio_field(hash)
|
340
|
+
end
|
341
|
+
ret
|
342
|
+
end
|
343
|
+
|
344
|
+
# Provides a generic HTML radio input tag.
|
345
|
+
# Normally, you would use multipe +radio_field+.
|
346
|
+
#
|
347
|
+
# ==== Example
|
348
|
+
# <%= radio_field :name => "radio_options", :value => "1", :label => "One" %>
|
349
|
+
# <%= radio_field :name => "radio_options", :value => "2", :label => "Two" %>
|
350
|
+
# <%= radio_field :name => "radio_options", :value => "3", :label => "Three", :checked => true %>
|
351
|
+
def radio_field(attrs = {})
|
352
|
+
attrs.merge!(:type => "radio")
|
353
|
+
attrs.delete(:checked) unless attrs[:checked]
|
354
|
+
attrs.add_html_class!("radio")
|
355
|
+
optional_label(attrs){self_closing_tag("input", attrs)}
|
356
|
+
end
|
357
|
+
|
358
|
+
# Provides a HTML textarea based on a resource attribute
|
359
|
+
# This is generally used within a resource block such as +form_for+
|
360
|
+
#
|
361
|
+
# ==== Example
|
362
|
+
# <% text_area_control :comments, :label => "Comments"
|
363
|
+
def text_area_control(col, attrs = {})
|
364
|
+
attrs ||= {}
|
365
|
+
errorify_field(attrs, col)
|
366
|
+
attrs.merge!(:id => control_id(col))
|
367
|
+
text_area_field(control_value(col), attrs.merge(:name => control_name(col)))
|
368
|
+
end
|
369
|
+
|
370
|
+
# Provides a generic HTML textarea tag.
|
371
|
+
#
|
372
|
+
# ==== Example
|
373
|
+
# <% text_area_field "my comments", :name => "comments", :label => "Comments" %>
|
374
|
+
def text_area_field(val, attrs = {})
|
375
|
+
val ||=""
|
376
|
+
optional_label(attrs) do
|
377
|
+
open_tag("textarea", attrs) +
|
378
|
+
val +
|
379
|
+
"</textarea>"
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Provides a generic HTML submit button.
|
384
|
+
#
|
385
|
+
# ==== Example
|
386
|
+
# <% submit_button "Process" %>
|
387
|
+
def submit_button(contents, attrs = {})
|
388
|
+
contents ||= "Submit"
|
389
|
+
attrs.merge!(:type => "submit")
|
390
|
+
tag("button", contents, attrs)
|
391
|
+
end
|
392
|
+
|
393
|
+
# Provides a generic HTML label.
|
394
|
+
#
|
395
|
+
# ==== Example
|
396
|
+
# <% label "Name", "", :for => "name" %>
|
397
|
+
# # => <label for="name">Name</label>
|
398
|
+
def label(name, contents = "", attrs = {})
|
399
|
+
tag("label", name.to_s + contents, attrs)
|
400
|
+
end
|
401
|
+
|
402
|
+
# Provides a generic HTML select.
|
403
|
+
#
|
404
|
+
# ==== Options
|
405
|
+
# +prompt+:: Adds an additional option tag with the provided string with no value.
|
406
|
+
# +selected+:: The value of a selected object, which may be either a string or an array.
|
407
|
+
# +include_blank+:: Adds an additional blank option tag with no value.
|
408
|
+
# +collection+:: The collection for the select options
|
409
|
+
# +text_method+:: Method to determine text of an option (as a symbol). Ex: :text_method => :name will call .name on your record object for what text to display.
|
410
|
+
# +value_method+:: Method to determine value of an option (as a symbol).
|
411
|
+
def select_field(attrs = {})
|
412
|
+
collection = attrs.delete(:collection)
|
413
|
+
option_attrs = {
|
414
|
+
:prompt => attrs.delete(:prompt),
|
415
|
+
:selected => attrs.delete(:selected),
|
416
|
+
:include_blank => attrs.delete(:include_blank),
|
417
|
+
:text_method => attrs.delete(:text_method),
|
418
|
+
:value_method => attrs.delete(:value_method)
|
419
|
+
}
|
420
|
+
optional_label(attrs) { open_tag('select', attrs) + options_from_collection_for_select(collection, option_attrs) + "</select>"}
|
421
|
+
end
|
422
|
+
|
423
|
+
# Provides a HTML select based on a resource attribute.
|
424
|
+
# This is generally used within a resource block such as +form_for+.
|
425
|
+
#
|
426
|
+
# ==== Example
|
427
|
+
# <% select_control :name, :collection => %w(one two three four) %>
|
428
|
+
def select_control(col, attrs = {})
|
429
|
+
attrs.merge!(:name => attrs[:name] || control_name(col))
|
430
|
+
attrs.merge!(:id => attrs[:id] || control_id(col))
|
431
|
+
attrs.merge!(:selected => attrs[:selected] || control_value(col))
|
432
|
+
errorify_field(attrs, col)
|
433
|
+
optional_label(attrs) { select_field(attrs) }
|
434
|
+
end
|
435
|
+
|
436
|
+
# Accepts a collection (hash, array, enumerable, your type) and returns a string of option tags.
|
437
|
+
# Given a collection where the elements respond to first and last (such as a two-element array),
|
438
|
+
# the "lasts" serve as option values and the "firsts" as option text. Hashes are turned into
|
439
|
+
# this form automatically, so the keys become "firsts" and values become lasts. If selected is
|
440
|
+
# specified, the matching "last" or element will get the selected option-tag. Selected may also
|
441
|
+
# be an array of values to be selected when using a multiple select.
|
442
|
+
#
|
443
|
+
# ==== Examples
|
444
|
+
# <%= options_for_select( [['apple','Apple Pie'],['orange','Orange Juice']], :selected => 'orange' )
|
445
|
+
# => <option value="apple">Apple Pie</option><option value="orange" selected="selected">Orange Juice</option>
|
446
|
+
#
|
447
|
+
# <%= options_for_select( [['apple','Apple Pie'],['orange','Orange Juice']], :selected => ['orange','apple'], :prompt => 'Select One' )
|
448
|
+
# => <option value="">Select One</option><option value="apple" selected="selected">Apple Pie</option><option value="orange" selected="selected">Orange Juice</option>
|
449
|
+
#
|
450
|
+
# ==== Options
|
451
|
+
# +selected+:: The value of a selected object, which may be either a string or an array.
|
452
|
+
# +prompt+:: Adds an addtional option tag with the provided string with no value.
|
453
|
+
# +include_blank+:: Adds an additional blank option tag with no value.
|
454
|
+
def options_for_select(collection, attrs = {})
|
455
|
+
prompt = attrs.delete(:prompt)
|
456
|
+
blank = attrs.delete(:include_blank)
|
457
|
+
selected = attrs.delete(:selected)
|
458
|
+
ret = String.new
|
459
|
+
ret << tag('option', prompt, :value => '') if prompt
|
460
|
+
ret << tag("option", '', :value => '') if blank
|
461
|
+
unless collection.blank?
|
462
|
+
if collection.is_a?(Hash)
|
463
|
+
collection.each do |label,group|
|
464
|
+
ret << open_tag("optgroup", :label => label.to_s.gsub(/\b[a-z]/) {|x| x.upcase}) +
|
465
|
+
options_for_select(group, :selected => selected) + "</optgroup>"
|
466
|
+
end
|
467
|
+
else
|
468
|
+
collection.each do |value,text|
|
469
|
+
options = Array(selected).include?(value) ? {:selected => 'selected'} : {}
|
470
|
+
ret << tag( 'option', text, {:value => value}.merge(options) )
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
return ret
|
476
|
+
end
|
477
|
+
|
478
|
+
# Returns a string of option tags that have been compiled by iterating over the collection and
|
479
|
+
# assigning the the result of a call to the value_method as the option value and the text_method
|
480
|
+
# as the option text. If selected_value is specified, the element returning a match on
|
481
|
+
# the value_method option will get the selected option tag.
|
482
|
+
#
|
483
|
+
# This method also also supports the automatic generation of optgroup tags by using a hash.
|
484
|
+
# ==== Examples
|
485
|
+
# If we had a collection of people within a @project object, and want to use 'id' as the value, and 'name'
|
486
|
+
# as the option content we could do something similar to this;
|
487
|
+
#
|
488
|
+
# <%= options_from_collection_for_select(@project.people, :value_method => "id", :text_method => "name") %>
|
489
|
+
# The iteration of the collection would create options in this manner;
|
490
|
+
# => <option value="#{person.id}">#{person.name}</option>
|
491
|
+
#
|
492
|
+
# <% @people = Person.find(:all).group_by( &:state )
|
493
|
+
# <%= options_for_select(@people, :text_method => 'full_name', :value_method => 'id', :selected => 3) %>
|
494
|
+
# => <optgroup label="Washington"><option value="1">Josh Martin</option><option value="2">John Doe</option></optgroup>
|
495
|
+
# => <optgroup label="Idaho"><option value="3" selected="selected">Jane Doe</option>
|
496
|
+
#
|
497
|
+
# ==== Options
|
498
|
+
# +text_method+:: Defines the method which will be used to provide the text of the option tags (required)
|
499
|
+
# +value_method+:: Defines the method which will be used to provide the value of the option tags (required)
|
500
|
+
# +selected+:: The value of a selected object, may be either a string or an array.
|
501
|
+
def options_from_collection_for_select(collection, attrs = {})
|
502
|
+
prompt = attrs.delete(:prompt)
|
503
|
+
blank = attrs.delete(:include_blank)
|
504
|
+
ret = String.new
|
505
|
+
if collection.is_a?(Hash)
|
506
|
+
ret << tag("option", prompt, :value => '') if prompt
|
507
|
+
ret << tag("option", '', :value => '') if blank
|
508
|
+
collection.each do |label, group|
|
509
|
+
# .gsub(/_/, " ").gsub(/\b[a-z]/) {|x| x.upcase}) == .humanize.titleize, which is no longer in -core
|
510
|
+
ret << open_tag("optgroup", :label => label.to_s.gsub(/_/, " ").gsub(/\b[a-z]/) {|x| x.upcase}) +
|
511
|
+
options_from_collection_for_select(group, attrs) + "</optgroup>"
|
512
|
+
end
|
513
|
+
return ret
|
514
|
+
else
|
515
|
+
text_method = attrs[:text_method]
|
516
|
+
value_method = attrs[:value_method]
|
517
|
+
selected_value = attrs[:selected]
|
518
|
+
|
519
|
+
text_method ||= :to_s
|
520
|
+
value_method ||= text_method
|
521
|
+
|
522
|
+
options_for_select((collection || []).inject([]) { |options, object|
|
523
|
+
options << [ object.send(value_method), object.send(text_method) ] },
|
524
|
+
:selected => selected_value, :include_blank => blank, :prompt => prompt
|
525
|
+
)
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
# Provides the ability to create quick fieldsets as blocks for your forms.
|
530
|
+
#
|
531
|
+
# ==== Example
|
532
|
+
# <% fieldset :legend => 'Customer Options' do -%>
|
533
|
+
# ...your form elements
|
534
|
+
# <% end -%>
|
535
|
+
#
|
536
|
+
# => <fieldset><legend>Customer Options</legend>...your form elements</fieldset>
|
537
|
+
#
|
538
|
+
# ==== Options
|
539
|
+
# +legend+:: The name of this fieldset which will be provided in a HTML legend tag.
|
540
|
+
def fieldset(attrs={}, &block)
|
541
|
+
legend = attrs.delete(:legend)
|
542
|
+
concat( open_tag('fieldset', attrs), block.binding )
|
543
|
+
concat( tag('legend', legend), block.binding ) if legend
|
544
|
+
concat(capture(&block), block.binding)
|
545
|
+
concat( "</fieldset>", block.binding)
|
546
|
+
end
|
547
|
+
|
548
|
+
# Provides a HTML file input for a resource attribute.
|
549
|
+
# This is generally used within a resource block such as +form_for+.
|
550
|
+
#
|
551
|
+
# ==== Example
|
552
|
+
# <% file_control :file, :label => "File" %>
|
553
|
+
def file_control(col, attrs = {})
|
554
|
+
errorify_field(attrs, col)
|
555
|
+
file_field(control_name_value(col, attrs))
|
556
|
+
end
|
557
|
+
|
558
|
+
# Provides a HTML file input
|
559
|
+
#
|
560
|
+
# ==== Example
|
561
|
+
# <% file_field :name => "file", :label => "File" %>
|
562
|
+
def file_field(attrs = {})
|
563
|
+
attrs.merge!(:type => "file")
|
564
|
+
attrs.add_html_class!("file")
|
565
|
+
optional_label(attrs) { self_closing_tag("input", attrs) }
|
566
|
+
end
|
567
|
+
|
568
|
+
def submit_field(attrs = {})
|
569
|
+
attrs.merge!(:type => :submit)
|
570
|
+
attrs[:name] ||= "submit"
|
571
|
+
self_closing_tag("input", attrs)
|
572
|
+
end
|
573
|
+
|
574
|
+
# Generates a delete button inside of a form.
|
575
|
+
#
|
576
|
+
# <%= delete_button :news_post, @news_post, 'Remove' %>
|
577
|
+
# <%= delete_button('/posts/24/comments/10') %>
|
578
|
+
#
|
579
|
+
# The HTML generated for this would be:
|
580
|
+
#
|
581
|
+
# <form method="post" action="/news_posts/4">
|
582
|
+
# <input type="hidden" value="delete" name="_method"/>
|
583
|
+
# <button type="submit">Remove</button>
|
584
|
+
# </form>
|
585
|
+
#
|
586
|
+
# <form method="post" action="/posts/24/comments/10">
|
587
|
+
# <input type="hidden" value="delete" name="_method"/>
|
588
|
+
# <button type="submit">Remove</button>
|
589
|
+
# </form>
|
590
|
+
def delete_button(symbol_or_string, obj = nil, contents = 'Delete', form_attrs = {}, button_attrs = {})
|
591
|
+
obj ||= instance_variable_get("@#{symbol_or_string}") if symbol_or_string.kind_of?(Symbol)
|
592
|
+
|
593
|
+
button_attrs[:type] = :submit
|
594
|
+
|
595
|
+
form_attrs.merge! :action => symbol_or_string.kind_of?(Symbol) ? url(symbol_or_string, obj) : symbol_or_string, :method => :delete
|
596
|
+
|
597
|
+
fake_form_method = set_form_method(form_attrs, obj)
|
598
|
+
|
599
|
+
button = ''
|
600
|
+
button << open_tag(:form, form_attrs)
|
601
|
+
button << generate_fake_form_method(fake_form_method)
|
602
|
+
button << tag(:button, contents, button_attrs)
|
603
|
+
button << '</form>'
|
604
|
+
button
|
605
|
+
end
|
606
|
+
|
607
|
+
private
|
608
|
+
# Fake out the browser to send back the method for RESTful stuff.
|
609
|
+
# Fall silently back to post if a method is given that is not supported here
|
610
|
+
def set_form_method(options = {}, obj = nil)
|
611
|
+
options[:method] ||= ((obj && obj.respond_to?(:new_record?) && !obj.new_record?) ? :put : :post)
|
612
|
+
if ![:get,:post].include?(options[:method])
|
613
|
+
fake_form_method = options[:method] if [:put, :delete].include?(options[:method])
|
614
|
+
options[:method] = :post
|
615
|
+
end
|
616
|
+
fake_form_method
|
617
|
+
end
|
618
|
+
|
619
|
+
def generate_fake_form_method(fake_form_method)
|
620
|
+
fake_form_method ? hidden_field(:name => "_method", :value => "#{fake_form_method}") : ""
|
621
|
+
end
|
622
|
+
|
623
|
+
def optional_label(attrs = {})
|
624
|
+
label = attrs.delete(:label) if attrs
|
625
|
+
if label
|
626
|
+
title = label.is_a?(Hash) ? label.delete(:title) : label
|
627
|
+
named = attrs[:id].blank? ? {} : {:for => attrs[:id]}
|
628
|
+
align = label.delete(:align) if label.is_a?(Hash)
|
629
|
+
align ||= ['radio', 'checkbox'].include?(attrs[:type].to_s) ? :right : :left
|
630
|
+
label_tag = label(title, '', label.is_a?(Hash) ? label.merge(named) : named)
|
631
|
+
if align && align.to_sym == :right
|
632
|
+
yield + label_tag
|
633
|
+
else
|
634
|
+
label_tag + yield
|
635
|
+
end
|
636
|
+
else
|
637
|
+
yield
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
def errorify_field(attrs, col)
|
642
|
+
attrs.add_html_class!("error") if @_obj.respond_to?(:errors) && @_obj.errors.on(col)
|
643
|
+
end
|
644
|
+
|
645
|
+
def set_multipart_attribute!(attrs = {})
|
646
|
+
attrs.merge!( :enctype => "multipart/form-data" ) if attrs.delete(:multipart)
|
647
|
+
end
|
648
|
+
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
class Merb::Controller
|
654
|
+
include Merb::Helpers::Form
|
655
|
+
end
|