merb-helpers 0.9.8
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 +20 -0
- data/README +180 -0
- data/Rakefile +77 -0
- data/TODO +5 -0
- data/lib/merb-helpers.rb +38 -0
- data/lib/merb-helpers/core_ext.rb +57 -0
- data/lib/merb-helpers/core_ext/numeric.rb +388 -0
- data/lib/merb-helpers/date_time_formatting.rb +127 -0
- data/lib/merb-helpers/date_time_helpers.rb +190 -0
- data/lib/merb-helpers/form/builder.rb +410 -0
- data/lib/merb-helpers/form/helpers.rb +448 -0
- data/lib/merb-helpers/form_helpers.rb +24 -0
- data/lib/merb-helpers/tag_helpers.rb +61 -0
- data/lib/merb-helpers/text_helpers.rb +61 -0
- data/lib/merb-helpers/time_dsl.rb +59 -0
- data/spec/core_ext_spec.rb +19 -0
- data/spec/fixture/app/controllers/application.rb +4 -0
- data/spec/fixture/app/controllers/bound_check_box.rb +2 -0
- data/spec/fixture/app/controllers/bound_file_field.rb +2 -0
- data/spec/fixture/app/controllers/bound_hidden_field.rb +2 -0
- data/spec/fixture/app/controllers/bound_option_tag.rb +2 -0
- data/spec/fixture/app/controllers/bound_password_field.rb +2 -0
- data/spec/fixture/app/controllers/bound_radio_button.rb +2 -0
- data/spec/fixture/app/controllers/bound_radio_group.rb +2 -0
- data/spec/fixture/app/controllers/bound_select.rb +2 -0
- data/spec/fixture/app/controllers/bound_text_area.rb +2 -0
- data/spec/fixture/app/controllers/bound_text_field.rb +2 -0
- data/spec/fixture/app/controllers/button.rb +2 -0
- data/spec/fixture/app/controllers/check_box.rb +2 -0
- data/spec/fixture/app/controllers/custom_builder.rb +2 -0
- data/spec/fixture/app/controllers/delete_button.rb +2 -0
- data/spec/fixture/app/controllers/exceptions.rb +25 -0
- data/spec/fixture/app/controllers/field_set.rb +2 -0
- data/spec/fixture/app/controllers/fields_for.rb +3 -0
- data/spec/fixture/app/controllers/file_field.rb +2 -0
- data/spec/fixture/app/controllers/foo.rb +23 -0
- data/spec/fixture/app/controllers/form.rb +2 -0
- data/spec/fixture/app/controllers/form_for.rb +2 -0
- data/spec/fixture/app/controllers/hidden_field.rb +2 -0
- data/spec/fixture/app/controllers/label.rb +3 -0
- data/spec/fixture/app/controllers/numeric_ext.rb +3 -0
- data/spec/fixture/app/controllers/option_tag.rb +2 -0
- data/spec/fixture/app/controllers/password_field.rb +2 -0
- data/spec/fixture/app/controllers/radio_button.rb +2 -0
- data/spec/fixture/app/controllers/radio_group.rb +2 -0
- data/spec/fixture/app/controllers/select.rb +2 -0
- data/spec/fixture/app/controllers/specs_controller.rb +11 -0
- data/spec/fixture/app/controllers/submit.rb +2 -0
- data/spec/fixture/app/controllers/tag_helper.rb +21 -0
- data/spec/fixture/app/controllers/text_area.rb +2 -0
- data/spec/fixture/app/controllers/text_field.rb +3 -0
- data/spec/fixture/app/helpers/global_helpers.rb +8 -0
- data/spec/fixture/app/models/fake_dm_model.rb +25 -0
- data/spec/fixture/app/models/first_generic_fake_model.rb +57 -0
- data/spec/fixture/app/models/second_generic_fake_model.rb +18 -0
- data/spec/fixture/app/models/third_generic_fake_model.rb +3 -0
- data/spec/fixture/app/views/bound_check_box_specs/basic.html.erb +4 -0
- data/spec/fixture/app/views/bound_check_box_specs/checked.html.erb +4 -0
- data/spec/fixture/app/views/bound_check_box_specs/errors.html.erb +4 -0
- data/spec/fixture/app/views/bound_check_box_specs/label.html.erb +3 -0
- data/spec/fixture/app/views/bound_check_box_specs/on_and_off.html.erb +3 -0
- data/spec/fixture/app/views/bound_check_box_specs/raise_value_error.html.erb +3 -0
- data/spec/fixture/app/views/bound_file_field_specs/additional_attributes.html.erb +3 -0
- data/spec/fixture/app/views/bound_file_field_specs/takes_string.html.erb +3 -0
- data/spec/fixture/app/views/bound_file_field_specs/with_label.html.erb +3 -0
- data/spec/fixture/app/views/bound_hidden_field_specs/basic.html.erb +3 -0
- data/spec/fixture/app/views/bound_hidden_field_specs/errors.html.erb +3 -0
- data/spec/fixture/app/views/bound_hidden_field_specs/hidden_error.html.erb +3 -0
- data/spec/fixture/app/views/bound_hidden_field_specs/label.html.erb +3 -0
- data/spec/fixture/app/views/bound_option_tag_specs/grouped.html.erb +3 -0
- data/spec/fixture/app/views/bound_option_tag_specs/nested.html.erb +3 -0
- data/spec/fixture/app/views/bound_option_tag_specs/text_and_value.html.erb +4 -0
- data/spec/fixture/app/views/bound_password_field_specs/attributes.html.erb +3 -0
- data/spec/fixture/app/views/bound_password_field_specs/basic.html.erb +3 -0
- data/spec/fixture/app/views/bound_password_field_specs/label.html.erb +3 -0
- data/spec/fixture/app/views/bound_radio_button_specs/basic.html.erb +3 -0
- data/spec/fixture/app/views/bound_radio_group_specs/basic.html.erb +3 -0
- data/spec/fixture/app/views/bound_radio_group_specs/hashes.html.erb +3 -0
- data/spec/fixture/app/views/bound_radio_group_specs/mixed.html.erb +4 -0
- data/spec/fixture/app/views/bound_radio_group_specs/override_id.html.erb +3 -0
- data/spec/fixture/app/views/bound_select_specs/basic.html.erb +3 -0
- data/spec/fixture/app/views/bound_select_specs/blank.html.erb +3 -0
- data/spec/fixture/app/views/bound_select_specs/multiple.html.erb +3 -0
- data/spec/fixture/app/views/bound_select_specs/prompt.html.erb +3 -0
- data/spec/fixture/app/views/bound_select_specs/with_options.html.erb +3 -0
- data/spec/fixture/app/views/bound_select_specs/with_options_with_blank.html.erb +3 -0
- data/spec/fixture/app/views/bound_text_area_specs/basic.html.erb +3 -0
- data/spec/fixture/app/views/bound_text_field_specs/basic.html.erb +3 -0
- data/spec/fixture/app/views/button_specs/button_with_label.html.erb +1 -0
- data/spec/fixture/app/views/button_specs/button_with_values.html.erb +1 -0
- data/spec/fixture/app/views/button_specs/disabled_button.html.erb +1 -0
- data/spec/fixture/app/views/check_box_specs/basic.html.erb +1 -0
- data/spec/fixture/app/views/check_box_specs/boolean.html.erb +1 -0
- data/spec/fixture/app/views/check_box_specs/disabled.html.erb +1 -0
- data/spec/fixture/app/views/check_box_specs/label.html.erb +1 -0
- data/spec/fixture/app/views/check_box_specs/on_off_is_boolean.html.erb +1 -0
- data/spec/fixture/app/views/check_box_specs/raise_unless_both_on_and_off.html.erb +2 -0
- data/spec/fixture/app/views/check_box_specs/raises_error_if_not_boolean.html.erb +1 -0
- data/spec/fixture/app/views/check_box_specs/raises_error_if_on_off_and_boolean_false.html.erb +1 -0
- data/spec/fixture/app/views/check_box_specs/simple.html.erb +1 -0
- data/spec/fixture/app/views/check_box_specs/to_string.html.erb +8 -0
- data/spec/fixture/app/views/check_box_specs/unchecked.html.erb +2 -0
- data/spec/fixture/app/views/custom_builder_specs/everything.html.erb +10 -0
- data/spec/fixture/app/views/delete_button_specs/delete_with_explicit_url.html.erb +1 -0
- data/spec/fixture/app/views/delete_button_specs/delete_with_extra_params.html.erb +1 -0
- data/spec/fixture/app/views/delete_button_specs/delete_with_label.html.erb +1 -0
- data/spec/fixture/app/views/delete_button_specs/simple_delete.html.erb +1 -0
- data/spec/fixture/app/views/exeptions/client_error.html.erb +37 -0
- data/spec/fixture/app/views/exeptions/internal_server_error.html.erb +216 -0
- data/spec/fixture/app/views/exeptions/not_acceptable.html.erb +38 -0
- data/spec/fixture/app/views/exeptions/not_found.html.erb +40 -0
- data/spec/fixture/app/views/fields_for_specs/basic.html.erb +3 -0
- data/spec/fixture/app/views/fields_for_specs/midstream.html.erb +7 -0
- data/spec/fixture/app/views/fields_for_specs/nil.html.erb +3 -0
- data/spec/fixture/app/views/fieldset_specs/legend.html.erb +3 -0
- data/spec/fixture/app/views/file_field_specs/disabled.html.erb +1 -0
- data/spec/fixture/app/views/file_field_specs/makes_multipart.html.erb +3 -0
- data/spec/fixture/app/views/file_field_specs/with_label.html.erb +1 -0
- data/spec/fixture/app/views/file_field_specs/with_values.html.erb +1 -0
- data/spec/fixture/app/views/foo/bar.html.erb +0 -0
- data/spec/fixture/app/views/form_for_specs/basic.html.erb +3 -0
- data/spec/fixture/app/views/form_specs/create_a_form.html.erb +3 -0
- data/spec/fixture/app/views/form_specs/create_a_multipart_form.html.erb +3 -0
- data/spec/fixture/app/views/form_specs/fake_delete_if_set.html.erb +3 -0
- data/spec/fixture/app/views/form_specs/fake_put_if_set.html.erb +3 -0
- data/spec/fixture/app/views/form_specs/get_if_set.html.erb +3 -0
- data/spec/fixture/app/views/form_specs/post_by_default.html.erb +3 -0
- data/spec/fixture/app/views/form_specs/resourceful_form.html.erb +3 -0
- data/spec/fixture/app/views/hidden_field_specs/basic.html.erb +1 -0
- data/spec/fixture/app/views/hidden_field_specs/disabled.html.erb +1 -0
- data/spec/fixture/app/views/hidden_field_specs/label.html.erb +1 -0
- data/spec/fixture/app/views/label_specs/basic.html.erb +1 -0
- data/spec/fixture/app/views/layout/application.html.erb +11 -0
- data/spec/fixture/app/views/numeric_ext_specs/minutes_to_hours.html.erb +1 -0
- data/spec/fixture/app/views/numeric_ext_specs/to_concurrency_default.html.erb +1 -0
- data/spec/fixture/app/views/numeric_ext_specs/two_digits.html.erb +1 -0
- data/spec/fixture/app/views/option_tag_specs/array.html.erb +1 -0
- data/spec/fixture/app/views/option_tag_specs/clean.html.erb +1 -0
- data/spec/fixture/app/views/option_tag_specs/collection.html.erb +1 -0
- data/spec/fixture/app/views/option_tag_specs/multiple_selects.html.erb +1 -0
- data/spec/fixture/app/views/option_tag_specs/no_extra_attributes.html.erb +1 -0
- data/spec/fixture/app/views/option_tag_specs/optgroups.html.erb +1 -0
- data/spec/fixture/app/views/option_tag_specs/selected.html.erb +1 -0
- data/spec/fixture/app/views/option_tag_specs/with_blank.html.erb +1 -0
- data/spec/fixture/app/views/option_tag_specs/with_prompt.html.erb +1 -0
- data/spec/fixture/app/views/password_field_specs/basic.html.erb +1 -0
- data/spec/fixture/app/views/password_field_specs/disabled.html.erb +1 -0
- data/spec/fixture/app/views/radio_button_specs/basic.html.erb +1 -0
- data/spec/fixture/app/views/radio_button_specs/disabled.html.erb +1 -0
- data/spec/fixture/app/views/radio_button_specs/label.html.erb +1 -0
- data/spec/fixture/app/views/radio_group_specs/attributes.html.erb +1 -0
- data/spec/fixture/app/views/radio_group_specs/basic.html.erb +1 -0
- data/spec/fixture/app/views/radio_group_specs/hash.html.erb +1 -0
- data/spec/fixture/app/views/radio_group_specs/specific_attributes.html.erb +1 -0
- data/spec/fixture/app/views/select_specs/blank.html.erb +1 -0
- data/spec/fixture/app/views/select_specs/multiple.html.erb +1 -0
- data/spec/fixture/app/views/submit_specs/disabled_submit.html.erb +1 -0
- data/spec/fixture/app/views/submit_specs/submit_with_label.html.erb +1 -0
- data/spec/fixture/app/views/submit_specs/submit_with_values.html.erb +1 -0
- data/spec/fixture/app/views/tag_helper/nested_tags.html.erb +5 -0
- data/spec/fixture/app/views/tag_helper/tag_with_attributes.html.erb +1 -0
- data/spec/fixture/app/views/tag_helper/tag_with_content.html.erb +1 -0
- data/spec/fixture/app/views/tag_helper/tag_with_content_in_the_block.html.erb +3 -0
- data/spec/fixture/app/views/text_area_specs/basic.html.erb +1 -0
- data/spec/fixture/app/views/text_area_specs/disabled.html.erb +1 -0
- data/spec/fixture/app/views/text_area_specs/label.html.erb +1 -0
- data/spec/fixture/app/views/text_area_specs/nil.html.erb +1 -0
- data/spec/fixture/app/views/text_field_specs/basic.html.erb +1 -0
- data/spec/fixture/app/views/text_field_specs/class.html.erb +1 -0
- data/spec/fixture/app/views/text_field_specs/disabled.html.erb +1 -0
- data/spec/fixture/app/views/text_field_specs/label.html.erb +1 -0
- data/spec/fixture/config/environments/development.rb +6 -0
- data/spec/fixture/config/environments/production.rb +5 -0
- data/spec/fixture/config/environments/test.rb +6 -0
- data/spec/fixture/config/init.rb +46 -0
- data/spec/fixture/config/rack.rb +11 -0
- data/spec/fixture/config/router.rb +36 -0
- data/spec/fixture/log/merb.main.pid +1 -0
- data/spec/fixture/log/merb_test.log +624 -0
- data/spec/fixture/public/images/merb.jpg +0 -0
- data/spec/fixture/public/merb.fcgi +4 -0
- data/spec/fixture/public/stylesheets/master.css +119 -0
- data/spec/merb_helpers_config_spec.rb +82 -0
- data/spec/merb_helpers_date_time_spec.rb +257 -0
- data/spec/merb_helpers_form_spec.rb +1266 -0
- data/spec/merb_helpers_tag_helper_spec.rb +46 -0
- data/spec/merb_helpers_text_spec.rb +67 -0
- data/spec/numeric_extlib_spec.rb +135 -0
- data/spec/ordinalize_spec.rb +51 -0
- data/spec/spec_helper.rb +149 -0
- data/spec/time_dsl_spec.rb +43 -0
- metadata +303 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
module DateAndTimeFormatting
|
|
2
|
+
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
include DateAndTimeFormatting::InstanceMethods
|
|
6
|
+
include OrdinalizedFormatting
|
|
7
|
+
extend DateAndTimeFormatting::ClassMethods
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module InstanceMethods
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Format a date/time instance using a defined format
|
|
15
|
+
#
|
|
16
|
+
# ==== Parameters
|
|
17
|
+
# format<Symbol>:: of the format key from Date.date_formats
|
|
18
|
+
#
|
|
19
|
+
# ==== Returns
|
|
20
|
+
# String:: formattred string
|
|
21
|
+
#
|
|
22
|
+
#--
|
|
23
|
+
# @public
|
|
24
|
+
def formatted(format = :default)
|
|
25
|
+
self.strftime(Date.formats[format])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module ClassMethods
|
|
31
|
+
|
|
32
|
+
@@formats = {
|
|
33
|
+
:db => "%Y-%m-%d %H:%M:%S",
|
|
34
|
+
:time => "%H:%M", # 21:12
|
|
35
|
+
:date => "%Y-%m-%d", # 2008-12-04
|
|
36
|
+
:short => "%d %b %H:%M", # 01 Sep 21:12
|
|
37
|
+
:long => "%B %d, %Y %H:%M",
|
|
38
|
+
:rfc822 => "%a, %d %b %Y %H:%M:%S %z"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Lists the date and time formats
|
|
42
|
+
#
|
|
43
|
+
# ==== Returns
|
|
44
|
+
# Hash:: a hash with all formats available
|
|
45
|
+
# --
|
|
46
|
+
# @public
|
|
47
|
+
def formats
|
|
48
|
+
@@formats
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Adds a date and time format
|
|
52
|
+
#
|
|
53
|
+
# ==== Parameters
|
|
54
|
+
# key<Symbol>:: name of the format
|
|
55
|
+
# format<Hash>:: time format to use
|
|
56
|
+
#
|
|
57
|
+
# ==== Returns
|
|
58
|
+
# Hash:: a hash with all formats available
|
|
59
|
+
# --
|
|
60
|
+
# @public
|
|
61
|
+
def add_format(key, format)
|
|
62
|
+
formats.merge!({key => format})
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Resets the date and time formats
|
|
67
|
+
# --
|
|
68
|
+
# @private
|
|
69
|
+
def reset_formats
|
|
70
|
+
original_formats = [:db, :time, :short, :date, :long, :long_ordinal, :rfc822]
|
|
71
|
+
formats = @@formats.delete_if{|format, v| !original_formats.include?(format)}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
module Ordinalize
|
|
79
|
+
# Ordinalize turns a number into an ordinal string used to denote the
|
|
80
|
+
# position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
|
|
81
|
+
#
|
|
82
|
+
# Examples
|
|
83
|
+
# 1.ordinalize # => "1st"
|
|
84
|
+
# 2.ordinalize # => "2nd"
|
|
85
|
+
# 1002.ordinalize # => "1002nd"
|
|
86
|
+
# 1003.ordinalize # => "1003rd"
|
|
87
|
+
def ordinalize
|
|
88
|
+
if (11..13).include?(self % 100)
|
|
89
|
+
"#{self}th"
|
|
90
|
+
else
|
|
91
|
+
case self % 10
|
|
92
|
+
when 1; "#{self}st"
|
|
93
|
+
when 2; "#{self}nd"
|
|
94
|
+
when 3; "#{self}rd"
|
|
95
|
+
else "#{self}th"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
Integer.send :include, Ordinalize
|
|
102
|
+
|
|
103
|
+
# Time.now.to_ordinalized_s :long
|
|
104
|
+
# => "February 28th, 2006 21:10"
|
|
105
|
+
module OrdinalizedFormatting
|
|
106
|
+
|
|
107
|
+
def to_ordinalized_s(format = :default)
|
|
108
|
+
format = Date.formats[format]
|
|
109
|
+
return self.to_s if format.nil?
|
|
110
|
+
strftime_ordinalized(format)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Gives you a relative date in an attractive format
|
|
114
|
+
#
|
|
115
|
+
# ==== Parameters
|
|
116
|
+
# format<String>:: strftime string used to formatt a time/date object
|
|
117
|
+
# locale<String, Symbol>:: An optional value which can be used by localization plugins
|
|
118
|
+
#
|
|
119
|
+
# ==== Returns
|
|
120
|
+
# String:: Ordinalized time/date object
|
|
121
|
+
#
|
|
122
|
+
# ==== Examples
|
|
123
|
+
# 5.days.ago.strftime_ordinalized('%b %d, %Y') # =>
|
|
124
|
+
def strftime_ordinalized(fmt, format=nil)
|
|
125
|
+
strftime(fmt.gsub(/(^|[^-])%d/, '\1_%d_')).gsub(/_(\d+)_/) { |s| s.to_i.ordinalize }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
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
|
+
|
|
20
|
+
def self.time_class
|
|
21
|
+
@@time_class
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# ==== Parameters
|
|
25
|
+
# format<Symbol>:: time format to use
|
|
26
|
+
# locale<String, Symbol>:: An optional value which can be used by localization plugins
|
|
27
|
+
#
|
|
28
|
+
# ==== Returns
|
|
29
|
+
# String:: a string used to format time using #strftime
|
|
30
|
+
def self.time_output(format, locale=nil)
|
|
31
|
+
@@time_output[format]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Gives you a relative date in an attractive format
|
|
35
|
+
#
|
|
36
|
+
# ==== Parameters
|
|
37
|
+
# time<~to_date>:: The Date or Time to test
|
|
38
|
+
# locale<String, Symbol>:: An optional value which can be used by localization plugins
|
|
39
|
+
#
|
|
40
|
+
# ==== Returns
|
|
41
|
+
# String:: Relative date
|
|
42
|
+
#
|
|
43
|
+
# ==== Examples
|
|
44
|
+
# relative_date(Time.now.utc) => "today"
|
|
45
|
+
# relative_date(5.days.ago) => "March 5th"
|
|
46
|
+
# relative_date(1.year.ago) => "March 10th, 2007"
|
|
47
|
+
def relative_date(time, locale=nil)
|
|
48
|
+
date = time.to_date
|
|
49
|
+
today = DateAndTime.time_class.now.to_date
|
|
50
|
+
if date == today
|
|
51
|
+
DateAndTime.time_output(:today, locale)
|
|
52
|
+
elsif date == (today - 1)
|
|
53
|
+
DateAndTime.time_output(:yesterday, locale)
|
|
54
|
+
elsif date == (today + 1)
|
|
55
|
+
DateAndTime.time_output(:tomorrow, locale)
|
|
56
|
+
else
|
|
57
|
+
fmt = DateAndTime.time_output(:initial_format, locale).dup
|
|
58
|
+
fmt << DateAndTime.time_output(:year_format, locale) unless date.year == today.year
|
|
59
|
+
time.strftime_ordinalized(fmt, locale)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Gives you a relative date span in an attractive format
|
|
64
|
+
#
|
|
65
|
+
# ==== Parameters
|
|
66
|
+
# times<~first,~last>:: The Dates or Times to test
|
|
67
|
+
#
|
|
68
|
+
# ==== Returns
|
|
69
|
+
# String:: The sexy relative date span
|
|
70
|
+
#
|
|
71
|
+
# ==== Examples
|
|
72
|
+
# relative_date([1.second.ago, 10.seconds.ago]) => "March 10th"
|
|
73
|
+
# relative_date([1.year.ago, 1.year.ago) => "March 10th, 2007"
|
|
74
|
+
# relative_date([Time.now, 1.day.from_now]) => "March 10th - 11th"
|
|
75
|
+
# relative_date([Time.now, 1.year.ago]) => "March 10th, 2007 - March 10th, 2008"
|
|
76
|
+
def relative_date_span(times)
|
|
77
|
+
times = [times.first, times.last].collect! { |t| t.to_date }
|
|
78
|
+
times.sort!
|
|
79
|
+
if times.first == times.last
|
|
80
|
+
relative_date(times.first)
|
|
81
|
+
else
|
|
82
|
+
first = times.first; last = times.last; now = DateAndTime.time_class.now
|
|
83
|
+
arr = [first.strftime_ordinalized('%b %d')]
|
|
84
|
+
arr << ", #{first.year}" unless first.year == last.year
|
|
85
|
+
arr << ' - '
|
|
86
|
+
arr << last.strftime('%b') << ' ' unless first.year == last.year && first.month == last.month
|
|
87
|
+
arr << last.day.ordinalize
|
|
88
|
+
arr << ", #{last.year}" unless first.year == last.year && last.year == now.year
|
|
89
|
+
arr.to_s
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Gives you a relative date span in an attractive format
|
|
94
|
+
#
|
|
95
|
+
# ==== Parameters
|
|
96
|
+
# times<~first,~last>:: The Dates or Times to test
|
|
97
|
+
#
|
|
98
|
+
# ==== Returns
|
|
99
|
+
# String:: The sexy relative time span
|
|
100
|
+
#
|
|
101
|
+
# ==== Examples
|
|
102
|
+
# relative_time_span([1.second.ago, 10.seconds.ago]) => "12:00 - 12:09 AM March 10th"
|
|
103
|
+
# relative_time_span([1.year.ago, 1.year.ago) => "12:09 AM March 10th, 2007"
|
|
104
|
+
# relative_time_span([Time.now, 13.hours.from_now]) => "12:09 AM - 1:09 PM March 10th"
|
|
105
|
+
# relative_time_span([Time.now, 1.year.ago]) => "12:09 AM March 10th, 2007 - 12:09 AM March 10th, 2008"
|
|
106
|
+
def relative_time_span(times)
|
|
107
|
+
times = [times.first, times.last].collect! { |t| t.to_time }
|
|
108
|
+
times.sort!
|
|
109
|
+
if times.first == times.last
|
|
110
|
+
"#{prettier_time(times.first)} #{relative_date(times.first)}"
|
|
111
|
+
elsif times.first.to_date == times.last.to_date
|
|
112
|
+
same_half = (times.first.hour/12 == times.last.hour/12)
|
|
113
|
+
"#{prettier_time(times.first, !same_half)} - #{prettier_time(times.last)} #{relative_date(times.first)}"
|
|
114
|
+
|
|
115
|
+
else
|
|
116
|
+
first = times.first; last = times.last; now = DateAndTime.time_class.now
|
|
117
|
+
arr = [prettier_time(first)]
|
|
118
|
+
arr << ' '
|
|
119
|
+
arr << first.strftime_ordinalized('%b %d')
|
|
120
|
+
arr << ", #{first.year}" unless first.year == last.year
|
|
121
|
+
arr << ' - '
|
|
122
|
+
arr << prettier_time(last)
|
|
123
|
+
arr << ' '
|
|
124
|
+
arr << last.strftime('%b') << ' ' unless first.year == last.year && first.month == last.month
|
|
125
|
+
arr << last.day.ordinalize
|
|
126
|
+
arr << ", #{last.year}" unless first.year == last.year && last.year == now.year
|
|
127
|
+
arr.to_s
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Condenses time... very similar to time_ago_in_words in ActionPack
|
|
132
|
+
#
|
|
133
|
+
# ==== Parameters
|
|
134
|
+
# from_time<~to_time>:: The Date or Time to start from
|
|
135
|
+
# to_time<~to_time>:: The Date or Time to go to, Defaults to Time.now.utc
|
|
136
|
+
# include_seconds<Boolean>:: Count the seconds initially, Defaults to false
|
|
137
|
+
# locale<String, Symbol>:: An optional value which can be used by localization plugins
|
|
138
|
+
#
|
|
139
|
+
# ==== Returns
|
|
140
|
+
# String:: The time distance
|
|
141
|
+
#
|
|
142
|
+
# ==== Examples
|
|
143
|
+
# time_lost_in_words(3.minutes.from_now) # => 3 minutes
|
|
144
|
+
# time_lost_in_words(Time.now - 15.hours) # => 15 hours
|
|
145
|
+
# time_lost_in_words(Time.now, 3.minutes.from_now) # => 3 minutes
|
|
146
|
+
# time_lost_in_words(Time.now) # => less than a minute
|
|
147
|
+
# time_lost_in_words(Time.now, Time.now, true) # => less than 5 seconds
|
|
148
|
+
#
|
|
149
|
+
def time_lost_in_words(from_time, to_time = Time.now.utc, include_seconds = false, locale=nil)
|
|
150
|
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
|
151
|
+
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
|
152
|
+
distance_in_minutes = (((to_time - from_time).abs)/60).round
|
|
153
|
+
distance_in_seconds = ((to_time - from_time).abs).round
|
|
154
|
+
|
|
155
|
+
case distance_in_minutes
|
|
156
|
+
when 0..1
|
|
157
|
+
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
|
|
158
|
+
case distance_in_seconds
|
|
159
|
+
when 0..4 then 'less than 5 seconds'
|
|
160
|
+
when 5..9 then 'less than 10 seconds'
|
|
161
|
+
when 10..19 then 'less than 20 seconds'
|
|
162
|
+
when 20..39 then 'half a minute'
|
|
163
|
+
when 40..59 then 'less than a minute'
|
|
164
|
+
else '1 minute'
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
when 2..44 then "#{distance_in_minutes} minutes"
|
|
168
|
+
when 45..89 then 'about 1 hour'
|
|
169
|
+
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
|
170
|
+
when 1440..2879 then '1 day'
|
|
171
|
+
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
|
172
|
+
when 43200..86399 then 'about 1 month'
|
|
173
|
+
when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
|
|
174
|
+
when 525600..1051199 then 'about 1 year'
|
|
175
|
+
else "over #{(distance_in_minutes / 525600).round} years"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
alias :time_ago_in_words :time_lost_in_words
|
|
179
|
+
|
|
180
|
+
def prettier_time(time, ampm=true, locale=nil)
|
|
181
|
+
time.strftime("%I:%M#{" %p" if ampm}").sub(/^0/, '')
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
class Merb::Controller
|
|
188
|
+
include Merb::Helpers::DateAndTime
|
|
189
|
+
end
|
|
190
|
+
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
load File.dirname(__FILE__) / ".." / "tag_helpers.rb"
|
|
2
|
+
|
|
3
|
+
module Merb::Helpers::Form::Builder
|
|
4
|
+
|
|
5
|
+
class Base
|
|
6
|
+
include Merb::Helpers::Tag
|
|
7
|
+
|
|
8
|
+
def initialize(obj, name, origin)
|
|
9
|
+
@obj, @origin = obj, origin
|
|
10
|
+
@name = name || @obj.class.name.snake_case.split("/").last
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def concat(attrs, &blk)
|
|
14
|
+
@origin.concat(@origin.capture(&blk), blk.binding)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def form(attrs = {}, &blk)
|
|
18
|
+
captured = @origin.capture(&blk)
|
|
19
|
+
fake_method_tag = process_form_attrs(attrs)
|
|
20
|
+
tag(:form, fake_method_tag + captured, attrs)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fieldset(attrs, &blk)
|
|
24
|
+
legend = (l_attr = attrs.delete(:legend)) ? tag(:legend, l_attr) : ""
|
|
25
|
+
tag(:fieldset, legend + @origin.capture(&blk), attrs)
|
|
26
|
+
# @origin.concat(contents, blk.binding)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
%w(text password hidden file).each do |kind|
|
|
30
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
31
|
+
def bound_#{kind}_field(method, attrs = {})
|
|
32
|
+
name = control_name(method)
|
|
33
|
+
update_bound_controls(method, attrs, "#{kind}")
|
|
34
|
+
unbound_#{kind}_field({:name => name, :value => @obj.send(method)}.merge(attrs))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def unbound_#{kind}_field(attrs)
|
|
38
|
+
update_unbound_controls(attrs, "#{kind}")
|
|
39
|
+
self_closing_tag(:input, {:type => "#{kind}"}.merge(attrs))
|
|
40
|
+
end
|
|
41
|
+
RUBY
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def bound_check_box(method, attrs = {})
|
|
45
|
+
name = control_name(method)
|
|
46
|
+
update_bound_controls(method, attrs, "checkbox")
|
|
47
|
+
unbound_check_box({:name => name}.merge(attrs))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def unbound_check_box(attrs)
|
|
51
|
+
update_unbound_controls(attrs, "checkbox")
|
|
52
|
+
if attrs.delete(:boolean)
|
|
53
|
+
on, off = attrs.delete(:on), attrs.delete(:off)
|
|
54
|
+
unbound_hidden_field(:name => attrs[:name], :value => off) <<
|
|
55
|
+
self_closing_tag(:input, {:type => "checkbox", :value => on}.merge(attrs))
|
|
56
|
+
else
|
|
57
|
+
self_closing_tag(:input, {:type => "checkbox"}.merge(attrs))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def bound_radio_button(method, attrs = {})
|
|
62
|
+
name = control_name(method)
|
|
63
|
+
update_bound_controls(method, attrs, "radio")
|
|
64
|
+
unbound_radio_button({:name => name, :value => @obj.send(method)}.merge(attrs))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def unbound_radio_button(attrs)
|
|
68
|
+
update_unbound_controls(attrs, "radio")
|
|
69
|
+
self_closing_tag(:input, {:type => "radio"}.merge(attrs))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def bound_radio_group(method, arr)
|
|
73
|
+
val = @obj.send(method)
|
|
74
|
+
arr.map do |attrs|
|
|
75
|
+
attrs = {:value => attrs} unless attrs.is_a?(Hash)
|
|
76
|
+
attrs[:checked] ||= (val == attrs[:value])
|
|
77
|
+
radio_group_item(method, attrs)
|
|
78
|
+
end.join
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def unbound_radio_group(arr, attrs = {})
|
|
82
|
+
arr.map do |ind_attrs|
|
|
83
|
+
ind_attrs = {:value => ind_attrs} unless ind_attrs.is_a?(Hash)
|
|
84
|
+
joined = attrs.merge(ind_attrs)
|
|
85
|
+
joined.merge!(:label => joined[:label] || joined[:value])
|
|
86
|
+
unbound_radio_button(joined)
|
|
87
|
+
end.join
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def bound_select(method, attrs = {})
|
|
91
|
+
name = control_name(method)
|
|
92
|
+
update_bound_controls(method, attrs, "select")
|
|
93
|
+
unbound_select({:name => name}.merge(attrs))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def unbound_select(attrs = {})
|
|
97
|
+
update_unbound_controls(attrs, "select")
|
|
98
|
+
attrs[:name] << "[]" if attrs[:multiple] && !(attrs[:name] =~ /\[\]$/)
|
|
99
|
+
tag(:select, options_for(attrs), attrs)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def bound_text_area(method, attrs = {})
|
|
103
|
+
name = "#{@name}[#{method}]"
|
|
104
|
+
update_bound_controls(method, attrs, "text_area")
|
|
105
|
+
unbound_text_area(@obj.send(method), {:name => name}.merge(attrs))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def unbound_text_area(contents, attrs)
|
|
109
|
+
update_unbound_controls(attrs, "text_area")
|
|
110
|
+
tag(:textarea, contents, attrs)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def button(contents, attrs)
|
|
114
|
+
update_unbound_controls(attrs, "button")
|
|
115
|
+
tag(:button, contents, attrs)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def submit(value, attrs)
|
|
119
|
+
attrs[:type] ||= "submit"
|
|
120
|
+
attrs[:name] ||= "submit"
|
|
121
|
+
attrs[:value] ||= value
|
|
122
|
+
update_unbound_controls(attrs, "submit")
|
|
123
|
+
self_closing_tag(:input, attrs)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def process_form_attrs(attrs)
|
|
129
|
+
method = attrs[:method]
|
|
130
|
+
|
|
131
|
+
# Unless the method is :get, fake out the method using :post
|
|
132
|
+
attrs[:method] = :post unless attrs[:method] == :get
|
|
133
|
+
# Use a fake PUT if the object is not new, otherwise use the method
|
|
134
|
+
# passed in. Defaults to :post if no method is set.
|
|
135
|
+
method ||= (@obj.respond_to?(:new_record?) && !@obj.new_record?) || (@obj.respond_to?(:new?) && !@obj.new?) ? :put : :post
|
|
136
|
+
|
|
137
|
+
attrs[:enctype] = "multipart/form-data" if attrs.delete(:multipart) || @multipart
|
|
138
|
+
|
|
139
|
+
method == :post || method == :get ? "" : fake_out_method(attrs, method)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# This can be overridden to use another method to fake out methods
|
|
143
|
+
def fake_out_method(attrs, method)
|
|
144
|
+
self_closing_tag(:input, :type => "hidden", :name => "_method", :value => method)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def update_bound_controls(method, attrs, type)
|
|
148
|
+
case type
|
|
149
|
+
when "checkbox"
|
|
150
|
+
update_bound_check_box(method, attrs)
|
|
151
|
+
when "select"
|
|
152
|
+
update_bound_select(method, attrs)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def update_bound_check_box(method, attrs)
|
|
157
|
+
raise ArgumentError, ":value can't be used with a bound_check_box" if attrs.has_key?(:value)
|
|
158
|
+
|
|
159
|
+
attrs[:boolean] = attrs.fetch(:boolean, true)
|
|
160
|
+
|
|
161
|
+
val = @obj.send(method)
|
|
162
|
+
attrs[:checked] = attrs.key?(:on) ? val == attrs[:on] : considered_true?(val)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def update_bound_select(method, attrs)
|
|
166
|
+
attrs[:value_method] ||= method
|
|
167
|
+
attrs[:text_method] ||= attrs[:value_method] || :to_s
|
|
168
|
+
attrs[:selected] ||= @obj.send(attrs[:value_method])
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def update_unbound_controls(attrs, type)
|
|
172
|
+
case type
|
|
173
|
+
when "checkbox"
|
|
174
|
+
update_unbound_check_box(attrs)
|
|
175
|
+
when "file"
|
|
176
|
+
@multipart = true
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
attrs[:disabled] ? attrs[:disabled] = "disabled" : attrs.delete(:disabled)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def update_unbound_check_box(attrs)
|
|
183
|
+
boolean = attrs[:boolean] || (attrs[:on] && attrs[:off]) ? true : false
|
|
184
|
+
|
|
185
|
+
case
|
|
186
|
+
when attrs.key?(:on) ^ attrs.key?(:off)
|
|
187
|
+
raise ArgumentError, ":on and :off must be specified together"
|
|
188
|
+
when (attrs[:boolean] == false) && (attrs.key?(:on))
|
|
189
|
+
raise ArgumentError, ":boolean => false cannot be used with :on and :off"
|
|
190
|
+
when boolean && attrs.key?(:value)
|
|
191
|
+
raise ArgumentError, ":value can't be used with a boolean checkbox"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
if attrs[:boolean] = boolean
|
|
195
|
+
attrs[:on] ||= "1"; attrs[:off] ||= "0"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
attrs[:checked] = "checked" if attrs.delete(:checked)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Accepts a collection (hash, array, enumerable, your type) and returns a string of option tags.
|
|
202
|
+
# Given a collection where the elements respond to first and last (such as a two-element array),
|
|
203
|
+
# the "lasts" serve as option values and the "firsts" as option text. Hashes are turned into
|
|
204
|
+
# this form automatically, so the keys become "firsts" and values become lasts. If selected is
|
|
205
|
+
# specified, the matching "last" or element will get the selected option-tag. Selected may also
|
|
206
|
+
# be an array of values to be selected when using a multiple select.
|
|
207
|
+
#
|
|
208
|
+
# ==== Parameters
|
|
209
|
+
# attrs<Hash>:: HTML attributes and options
|
|
210
|
+
#
|
|
211
|
+
# ==== Options
|
|
212
|
+
# +selected+:: The value of a selected object, which may be either a string or an array.
|
|
213
|
+
# +prompt+:: Adds an addtional option tag with the provided string with no value.
|
|
214
|
+
# +include_blank+:: Adds an additional blank option tag with no value.
|
|
215
|
+
#
|
|
216
|
+
# ==== Returns
|
|
217
|
+
# String:: HTML
|
|
218
|
+
#
|
|
219
|
+
# ==== Examples
|
|
220
|
+
# <%= options_for [["apple", "Apple Pie"], ["orange", "Orange Juice"]], :selected => "orange"
|
|
221
|
+
# => <option value="apple">Apple Pie</option><option value="orange" selected="selected">Orange Juice</option>
|
|
222
|
+
#
|
|
223
|
+
# <%= options_for [["apple", "Apple Pie"], ["orange", "Orange Juice"]], :selected => ["orange", "apple"], :prompt => "Select One"
|
|
224
|
+
# => <option value="">Select One</option><option value="apple" selected="selected">Apple Pie</option><option value="orange" selected="selected">Orange Juice</option>
|
|
225
|
+
def options_for(attrs)
|
|
226
|
+
blank, prompt = attrs.delete(:include_blank), attrs.delete(:prompt)
|
|
227
|
+
b = blank || prompt ? tag(:option, prompt || "", :value => "") : ""
|
|
228
|
+
|
|
229
|
+
# yank out the options attrs
|
|
230
|
+
collection, selected, text_method, value_method =
|
|
231
|
+
attrs.extract!(:collection, :selected, :text_method, :value_method)
|
|
232
|
+
|
|
233
|
+
# if the collection is a Hash, optgroups are a-coming
|
|
234
|
+
if collection.is_a?(Hash)
|
|
235
|
+
([b] + collection.map do |g,col|
|
|
236
|
+
tag(:optgroup, options(col, text_method, value_method, selected), :label => g)
|
|
237
|
+
end).join
|
|
238
|
+
else
|
|
239
|
+
options(collection || [], text_method, value_method, selected, b)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def options(col, text_meth, value_meth, sel, b = nil)
|
|
244
|
+
([b] + col.map do |item|
|
|
245
|
+
text_meth = text_meth && item.respond_to?(text_meth) ? text_meth : :last
|
|
246
|
+
value_meth = value_meth && item.respond_to?(value_meth) ? value_meth : :first
|
|
247
|
+
|
|
248
|
+
text = item.is_a?(String) ? item : item.send(text_meth)
|
|
249
|
+
value = item.is_a?(String) ? item : item.send(value_meth)
|
|
250
|
+
|
|
251
|
+
option_attrs = {:value => value}
|
|
252
|
+
if sel.is_a?(Array)
|
|
253
|
+
option_attrs.merge!(:selected => "selected") if value.in? sel
|
|
254
|
+
else
|
|
255
|
+
option_attrs.merge!(:selected => "selected") if value == sel
|
|
256
|
+
end
|
|
257
|
+
tag(:option, text, option_attrs)
|
|
258
|
+
end).join
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def radio_group_item(method, attrs)
|
|
262
|
+
attrs.merge!(:checked => "checked") if attrs[:checked]
|
|
263
|
+
bound_radio_button(method, attrs)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def considered_true?(value)
|
|
267
|
+
value && value != "0" && value != 0
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def control_name(method)
|
|
271
|
+
"#{@name}[#{method}]"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def add_css_class(attrs, new_class)
|
|
275
|
+
attrs[:class] = attrs[:class] ? "#{attrs[:class]} #{new_class}" : new_class
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
class Form < Base
|
|
280
|
+
def label(contents, attrs = {})
|
|
281
|
+
if contents.is_a?(Hash)
|
|
282
|
+
attrs = contents
|
|
283
|
+
contents = attrs.delete(:label)
|
|
284
|
+
end
|
|
285
|
+
if contents
|
|
286
|
+
for_attr = attrs[:id] ? {:for => attrs[:id]} : {}
|
|
287
|
+
if contents.is_a?(Hash)
|
|
288
|
+
attrs = contents
|
|
289
|
+
contents = attrs.delete(:title)
|
|
290
|
+
for_attr = for_attr.merge(attrs)
|
|
291
|
+
end
|
|
292
|
+
tag(:label, contents, for_attr)
|
|
293
|
+
else
|
|
294
|
+
""
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
%w(text password file).each do |kind|
|
|
299
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
300
|
+
def unbound_#{kind}_field(attrs = {})
|
|
301
|
+
label(attrs) + super
|
|
302
|
+
end
|
|
303
|
+
RUBY
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def unbound_check_box(attrs = {})
|
|
307
|
+
label_text = label(attrs)
|
|
308
|
+
super + label_text
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def unbound_hidden_field(attrs = {})
|
|
312
|
+
attrs.delete(:label)
|
|
313
|
+
super
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def unbound_radio_button(attrs = {})
|
|
317
|
+
label_text = label(attrs)
|
|
318
|
+
super + label_text
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def unbound_select(attrs = {})
|
|
322
|
+
label(attrs) + super
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def unbound_text_area(contents, attrs = {})
|
|
326
|
+
label(attrs) + super
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def button(contents, attrs = {})
|
|
330
|
+
label(attrs) + super
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def submit(value, attrs = {})
|
|
334
|
+
label(attrs) + super
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
private
|
|
338
|
+
|
|
339
|
+
def update_bound_controls(method, attrs, type)
|
|
340
|
+
attrs.merge!(:id => "#{@name}_#{method}") unless attrs[:id]
|
|
341
|
+
super
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def update_unbound_controls(attrs, type)
|
|
345
|
+
case type
|
|
346
|
+
when "text", "radio", "password", "hidden", "checkbox", "file"
|
|
347
|
+
add_css_class(attrs, type)
|
|
348
|
+
end
|
|
349
|
+
super
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def radio_group_item(method, attrs)
|
|
353
|
+
unless attrs[:id]
|
|
354
|
+
attrs.merge!(:id => "#{@name}_#{method}_#{attrs[:value]}")
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
attrs.merge!(:label => attrs[:label] || attrs[:value])
|
|
358
|
+
super
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
module Errorifier
|
|
363
|
+
def error_messages_for(obj, error_class, build_li, header, before)
|
|
364
|
+
obj ||= @obj
|
|
365
|
+
return "" unless obj.respond_to?(:errors)
|
|
366
|
+
|
|
367
|
+
sequel = !obj.errors.respond_to?(:each)
|
|
368
|
+
errors = sequel ? obj.errors.full_messages : obj.errors
|
|
369
|
+
|
|
370
|
+
return "" if errors.empty?
|
|
371
|
+
|
|
372
|
+
header_message = header % [errors.size, errors.size == 1 ? "" : "s"]
|
|
373
|
+
markup = %Q{<div class='#{error_class}'>#{header_message}<ul>}
|
|
374
|
+
errors.each {|err| markup << (build_li % (sequel ? err : err.join(" ")))}
|
|
375
|
+
markup << %Q{</ul></div>}
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
private
|
|
379
|
+
|
|
380
|
+
def update_bound_controls(method, attrs, type)
|
|
381
|
+
if @obj.errors.on(method.to_sym)
|
|
382
|
+
add_css_class(attrs, "error")
|
|
383
|
+
end
|
|
384
|
+
super
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
class FormWithErrors < Form
|
|
389
|
+
include Errorifier
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
module Resourceful
|
|
393
|
+
private
|
|
394
|
+
|
|
395
|
+
def process_form_attrs(attrs)
|
|
396
|
+
attrs[:action] ||= @origin.url(@name, @obj) if @origin
|
|
397
|
+
super
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
class ResourcefulForm < Form
|
|
402
|
+
include Resourceful
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
class ResourcefulFormWithErrors < FormWithErrors
|
|
406
|
+
include Errorifier
|
|
407
|
+
include Resourceful
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
end
|