thorero-helpers 0.5.0.11 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|