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 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