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 CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Yehuda Katz
1
+ Copyright (c) 2007 YOUR NAME
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README CHANGED
@@ -1,4 +1,16 @@
1
- merb-laszlo
2
- ===========
1
+ merb_helpers
2
+ =================
3
3
 
4
- A plugin for the Merb framework that provides support for Laszlo
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 = "Merb plugin that provides support for Laszlo."
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.5.0" + PKG_BUILD
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
@@ -0,0 +1,5 @@
1
+ TODO:
2
+ Fix LICENSE with your name
3
+ Fix Rakefile with your name and contact info
4
+ Add your code to lib/merb_helpers.rb
5
+ Add your Merb rake tasks to lib/merb_helpers/merbtasks.rb
@@ -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