ramaze 2010.06.18 → 2011.01

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.
Files changed (95) hide show
  1. data/.gitignore +1 -0
  2. data/MANIFEST +9 -16
  3. data/README.md +37 -30
  4. data/Rakefile +5 -1
  5. data/TODO.md +19 -0
  6. data/doc/AUTHORS +5 -1
  7. data/doc/CHANGELOG +3553 -3272
  8. data/doc/tutorial/todolist.html +1512 -1512
  9. data/examples/app/blog/app.rb +2 -0
  10. data/examples/app/todolist/controller/init.rb +1 -2
  11. data/examples/app/wiktacular/mkd/main/2007-07-20_19-21-12.mkd +1 -1
  12. data/examples/app/wiktacular/mkd/main/2007-07-20_19-23-10.mkd +1 -1
  13. data/examples/app/wiktacular/mkd/main/2007-07-20_19-45-07.mkd +1 -1
  14. data/examples/app/wiktacular/mkd/main/current.mkd +1 -1
  15. data/examples/app/wiktacular/mkd/testing/2007-07-20_16-43-46.mkd +1 -1
  16. data/examples/app/wiktacular/mkd/testing/2007-07-20_19-43-50.mkd +2 -2
  17. data/examples/app/wiktacular/mkd/testing/2007-07-21_18-47-08.mkd +16 -16
  18. data/examples/app/wiktacular/mkd/testing/2007-07-21_18-47-54.mkd +16 -16
  19. data/examples/app/wiktacular/mkd/testing/current.mkd +16 -16
  20. data/lib/proto/model/init.rb +1 -1
  21. data/lib/proto/public/js/jquery.js +2034 -1095
  22. data/lib/proto/start.rb +2 -0
  23. data/lib/proto/view/index.xhtml +3 -3
  24. data/lib/ramaze.rb +1 -2
  25. data/lib/ramaze/cache.rb +1 -0
  26. data/lib/ramaze/cache/sequel.rb +131 -37
  27. data/lib/ramaze/controller.rb +1 -0
  28. data/lib/ramaze/gestalt.rb +75 -46
  29. data/lib/ramaze/helper.rb +1 -0
  30. data/lib/ramaze/helper/auth.rb +38 -4
  31. data/lib/ramaze/helper/blue_form.rb +498 -78
  32. data/lib/ramaze/helper/cache.rb +2 -2
  33. data/lib/ramaze/helper/csrf.rb +225 -0
  34. data/lib/ramaze/helper/erector.rb +67 -9
  35. data/lib/ramaze/helper/flash.rb +4 -2
  36. data/lib/ramaze/helper/gestalt.rb +2 -0
  37. data/lib/ramaze/helper/gravatar.rb +1 -1
  38. data/lib/ramaze/helper/localize.rb +4 -0
  39. data/lib/ramaze/helper/send_file.rb +30 -0
  40. data/lib/ramaze/helper/thread.rb +5 -0
  41. data/lib/ramaze/helper/user.rb +4 -3
  42. data/lib/ramaze/helper/xhtml.rb +87 -8
  43. data/lib/ramaze/log.rb +13 -0
  44. data/lib/ramaze/log/analogger.rb +15 -5
  45. data/lib/ramaze/log/growl.rb +28 -13
  46. data/lib/ramaze/log/hub.rb +12 -4
  47. data/lib/ramaze/log/informer.rb +28 -11
  48. data/lib/ramaze/log/knotify.rb +7 -2
  49. data/lib/ramaze/log/logger.rb +12 -4
  50. data/lib/ramaze/log/logging.rb +40 -14
  51. data/lib/ramaze/log/rotatinginformer.rb +47 -23
  52. data/lib/ramaze/log/syslog.rb +37 -31
  53. data/lib/ramaze/log/xosd.rb +7 -4
  54. data/lib/ramaze/middleware_compiler.rb +2 -2
  55. data/lib/ramaze/snippets/fiber.rb +63 -63
  56. data/lib/ramaze/snippets/ramaze/lru_hash.rb +1 -1
  57. data/lib/ramaze/tool/bin.rb +1 -1
  58. data/lib/ramaze/version.rb +1 -1
  59. data/lib/ramaze/view.rb +4 -4
  60. data/lib/ramaze/view/erector.rb +88 -13
  61. data/ramaze.gemspec +65 -65
  62. data/spec/ramaze/bin/ramaze.rb +1 -1
  63. data/spec/ramaze/cache/localmemcache.rb +20 -12
  64. data/spec/ramaze/cache/sequel.rb +19 -19
  65. data/spec/ramaze/helper/blue_form.rb +549 -257
  66. data/spec/ramaze/helper/csrf.rb +109 -0
  67. data/spec/ramaze/helper/httpdigest.rb +31 -29
  68. data/spec/ramaze/helper/user.rb +1 -1
  69. data/spec/ramaze/helper/xhtml.rb +17 -0
  70. data/spec/ramaze/log/growl.rb +34 -0
  71. data/spec/ramaze/log/informer.rb +1 -0
  72. data/spec/ramaze/view/erector.rb +49 -71
  73. data/spec/ramaze/view/erector/external_view.erector +5 -0
  74. data/spec/ramaze/view/erector/index.erector +5 -0
  75. data/spec/ramaze/view/erector/layout.erector +13 -3
  76. data/spec/ramaze/view/erector/tables.erector +23 -0
  77. data/spec/ramaze/view/erector/view.erector +6 -0
  78. data/tasks/git.rake +2 -2
  79. metadata +133 -176
  80. data/examples/helpers/form_with_sequel.rb +0 -24
  81. data/examples/helpers/nitro_form.rb +0 -23
  82. data/lib/ramaze/helper/form.rb +0 -133
  83. data/lib/ramaze/helper/nitroform.rb +0 -14
  84. data/lib/ramaze/helper/pager.rb +0 -367
  85. data/lib/ramaze/helper/partial.rb +0 -100
  86. data/lib/ramaze/helper/sequel.rb +0 -55
  87. data/lib/ramaze/helper/sequel_form.rb +0 -284
  88. data/lib/vendor/etag.rb +0 -22
  89. data/spec/ramaze/helper/form.rb +0 -360
  90. data/spec/ramaze/helper/pager.rb +0 -96
  91. data/spec/ramaze/helper/sequel_form.rb +0 -94
  92. data/spec/ramaze/view/erector/external.erector +0 -1
  93. data/spec/ramaze/view/erector/invoke_helper_method.erector +0 -1
  94. data/spec/ramaze/view/erector/strict_xhtml.erector +0 -3
  95. data/spec/ramaze/view/erector/sum.erector +0 -1
data/lib/ramaze/helper.rb CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'innate/helper'
5
5
 
6
+ # TODO: Describe what this module does besides loading a few default helper.
6
7
  module Ramaze
7
8
  Helper = Innate::Helper
8
9
  Innate::HelpersHelper.options.paths << File.dirname(__FILE__)
@@ -8,6 +8,13 @@ module Ramaze
8
8
  # Please have a look at the docs of Auth#auth_login.
9
9
  #
10
10
  # If you want to do authentication with a model see Helper::User instead.
11
+
12
+ ##
13
+ # The Auth helper can be used for authentication without using a model.
14
+ # This can be useful when working with very basic applications that don't require database access.
15
+ #
16
+ # If you're looking for a way to do authentication using a model you should take a look at Helper::User instead.
17
+ #
11
18
  module Auth
12
19
  Helper::LOOKUP << self
13
20
  include Ramaze::Traited
@@ -19,7 +26,12 @@ module Ramaze
19
26
  def self.included(into)
20
27
  into.helper(:stack)
21
28
  end
22
-
29
+
30
+ ##
31
+ # Log a user in based on the :username and :password key in the request hash.
32
+ #
33
+ # @return [String] The login template in case the user's login data was incorrect.
34
+ #
23
35
  def login
24
36
  return auth_template if trait[:auth_post_only] and !request.post?
25
37
  @username, password = request[:username, :password]
@@ -27,6 +39,9 @@ module Ramaze
27
39
  return auth_template
28
40
  end
29
41
 
42
+ ##
43
+ # Log the user out and redirect him back to the previous page.
44
+ #
30
45
  def logout
31
46
  auth_logout
32
47
  answer(request.referer)
@@ -34,16 +49,29 @@ module Ramaze
34
49
 
35
50
  private
36
51
 
52
+ ##
53
+ # Validate the user's session and redirect him/her to the login page in case the user isn't logged in.
54
+ #
37
55
  def login_required
38
56
  call(r(:login)) unless logged_in?
39
57
  end
40
58
 
41
- # @return [true false] whether user is logged in right now
59
+ ##
60
+ # Validate the user's session and return a boolean that indicates if the user is logged in or not.
61
+ #
62
+ # @return [true false] Whether user is logged in right now
63
+ #
42
64
  def logged_in?
43
65
  !!session[:logged_in]
44
66
  end
45
67
 
46
- # @return
68
+ ##
69
+ # Try to log the user in based on the username and password.
70
+ # This method is called by the login() method and shouldn't be called directly.
71
+ #
72
+ # @param [String] user The users's username.
73
+ # @param [String] pass The user's password.
74
+ #
47
75
  def auth_login(user, pass)
48
76
  return unless user and pass
49
77
  return if user.empty? or pass.empty?
@@ -62,12 +90,18 @@ module Ramaze
62
90
  session[:username] = user
63
91
  end
64
92
 
93
+ ##
94
+ # Remove the session items that specified that the user was logged in.
95
+ #
65
96
  def auth_logout
66
97
  session.delete(:logged_in)
67
98
  session.delete(:username)
68
99
  end
69
100
 
70
- # @return [String] template for auth
101
+ ##
102
+ # Method that returns a small form that can be used for logging in.
103
+ #
104
+ # @return [String] The login form.
71
105
  def auth_template
72
106
  <<-TEMPLATE.strip!
73
107
  <form method="post" action="#{r(:login)}">
@@ -3,15 +3,90 @@ require 'ramaze/gestalt'
3
3
 
4
4
  module Ramaze
5
5
  module Helper
6
- # This helper tries to be an even better way to build forms
7
- # programmatically, see the specs for lots of examples.
6
+ ##
7
+ # == Introduction
8
+ #
9
+ # The BlueForm helper tries to be an even better way to build forms programmatically.
10
+ # By using a simple block you can quickly create all the required elements for your form.
11
+ #
12
+ # Since November 2010 the BlueForm helper works different. You can now specify an object as the first
13
+ # parameter of the form_for() method. This object will be used to retrieve the values of each field.
14
+ # This means that you can directly pass a database result object to the form and no longer have
15
+ # to manually specify values. However, you can still specify your own values if you want.
16
+ #
17
+ # Old behaviour:
18
+ #
19
+ # form_for(:method => :post) do |f|
20
+ # f.input_text 'Username', :username, 'Chuck Norris'
21
+ # end
22
+ #
23
+ # New behaviour:
24
+ #
25
+ # # @data is an object that contains an instance variable named "username".
26
+ # # This variable contains the value "Chuck Norris".
27
+ # form_for(@data, :method => :post) do |f|
28
+ # f.input_text 'Username', :username
29
+ # end
30
+ #
31
+ # == Form Data
32
+ #
33
+ # As stated earlier it's possible to pass an object to the form_for() method. What kind of object this is,
34
+ # a database result object or an OpenStruct object doesn't matter as long as the attributes can be accessed
35
+ # outside of the object (this can be done using attr_readers). This makes it extremely easy to directly pass
36
+ # a result object from your favourite ORM. Example:
37
+ #
38
+ # @data = User[1]
39
+ #
40
+ # form_for(@data, :method => :post) do |f|
41
+ # f.input_text 'Username', :username
42
+ # end
43
+ #
44
+ # If you don't want to use an object you can simply set the first parameter to nil.
45
+ #
46
+ # == HTML Output
47
+ #
48
+ # The form helper uses Gestalt, Ramaze's custom HTML builder that works somewhat like Erector.
49
+ # The output is very minimalistic, elements such as legends and fieldsets have to be added manually.
50
+ # Each combination of a label and input element will be wrapped in <p> tags.
51
+ #
52
+ # When using the form helper as a block in your templates it's important to remember that the
53
+ # result is returned and not displayed in the browser directly. When using Etanni this would result in
54
+ # something like the following:
55
+ #
56
+ # #{
57
+ # form_for(@result, :method => :post) do |f| do
58
+ # f.input_text 'Text label', :textname, 'Chunky bacon!'
59
+ # end
60
+ # }
61
+ #
62
+ # @example
63
+ #
64
+ # form_for(@data, :method => :post) do |f|
65
+ # f.input_text 'Username', :username
66
+ # end
67
+ #
8
68
  module BlueForm
9
- def form(options = {}, &block)
10
- form = Form.new(options)
69
+ ##
70
+ # The form method generates the basic structure of the form. It should be called
71
+ # using a block and it's return value should be manually sent to the browser (since it does not echo the value).
72
+ #
73
+ # @param [Object] form_values Object containing the values for each form field.
74
+ # @param [Hash] options Hash containing any additional form attributes such as the method, action, enctype and so on.
75
+ # @param [Block] block Block containing the elements of the form such as password fields, textareas and so on.
76
+ #
77
+ def form_for(form_values, options = {}, &block)
78
+ form = Form.new(form_values, options)
11
79
  form.build(form_errors, &block)
12
80
  form
13
81
  end
14
82
 
83
+ ##
84
+ # Manually add a new error to the form_errors key in the flash hash.
85
+ # The first parameter is the name of the form field and the second parameter is the custom message.
86
+ #
87
+ # @param [String] name The name of the form field to which the error belongs.
88
+ # @param [String] message The custom error message to show.
89
+ #
15
90
  def form_error(name, message)
16
91
  if respond_to?(:flash)
17
92
  old = flash[:form_errors] || {}
@@ -21,6 +96,12 @@ module Ramaze
21
96
  end
22
97
  end
23
98
 
99
+ ##
100
+ # Returns the hash containing all existing errors and allows other methods to set
101
+ # new errors by using this method as if it were a hash.
102
+ #
103
+ # @return [Array] All form errors.
104
+ #
24
105
  def form_errors
25
106
  if respond_to?(:flash)
26
107
  flash[:form_errors] ||= {}
@@ -29,42 +110,122 @@ module Ramaze
29
110
  end
30
111
  end
31
112
 
113
+ ##
114
+ # Retrieve all the form errors for the specified model and add them to the flash hash.
115
+ #
116
+ # @param [Object] obj An object of a model that contains form errors.
117
+ #
32
118
  def form_errors_from_model(obj)
33
- obj.errors.each do |key, value|
34
- form_error(key.to_s, value.first % key)
119
+ if obj.respond_to?(:errors)
120
+ obj.errors.each do |key, value|
121
+ if value.respond_to?(:first)
122
+ value = value.first
123
+ end
124
+
125
+ form_error(key.to_s, value % key)
126
+ end
35
127
  end
36
128
  end
37
129
 
38
- # Note that an instance of this class is not thread-safe, so you should
39
- # modify it only within one thread of execution
130
+ ##
131
+ # Main form class that contains all the required methods to generate form specific tags,
132
+ # such as textareas and select boxes. Do note that this class is not thread-safe so you should
133
+ # modify it only within one thread of execution.
134
+ #
40
135
  class Form
41
136
  attr_reader :g
42
-
43
- def initialize(options)
44
- @form_args = options.dup
45
- @g = Gestalt.new
137
+ attr_reader :form_values
138
+
139
+ ##
140
+ # Constructor method that generates an instance of the Form class.
141
+ #
142
+ # @param [Object] form_values Object containing the values for each form field.
143
+ # @param [Hash] options A hash containing any additional form attributes.
144
+ # @return [Object] An instance of the Form class.
145
+ #
146
+ def initialize(form_values, options)
147
+ @form_values = form_values
148
+ @form_args = options.dup
149
+ @g = Gestalt.new
46
150
  end
47
151
 
152
+ ##
153
+ # Builds the form by generating the opening/closing tags and executing
154
+ # the methods in the block.
155
+ #
156
+ # @param [Hash] form_errors Hash containing all form errors (if any).
157
+ #
48
158
  def build(form_errors = {})
49
- @form_errors = form_errors
50
-
159
+ # Convert all the keys in form_errors to strings and
160
+ # retrieve the correct values in case
161
+ @form_errors = {}
162
+
163
+ form_errors.each do |key, value|
164
+ if value.respond_to?(:first)
165
+ value = value.first
166
+ end
167
+
168
+ @form_errors[key.to_s] = value
169
+ end
170
+
51
171
  @g.form(@form_args) do
52
172
  if block_given?
53
- @g.fieldset do
54
- yield self
55
- end
173
+ yield self
56
174
  end
57
175
  end
58
176
  end
59
177
 
178
+ ##
179
+ # Generate a <legend> tag.
180
+ #
181
+ # @param [String] text The text to display inside the legend tag.
182
+ # @example
183
+ #
184
+ # form_for(@data, :method => :post) do |f|
185
+ # f.legend 'Ramaze rocks!'
186
+ # end
187
+ #
60
188
  def legend(text)
61
189
  @g.legend(text)
62
190
  end
191
+
192
+ ##
193
+ # Generate a fieldset tag.
194
+ #
195
+ # @param [Block] &block The form elements to display inside the fieldset.
196
+ # @example
197
+ #
198
+ # form_for(@data, :method => :post) do |f|
199
+ # f.fieldset do
200
+ # f.legend 'Hello, world!'
201
+ # end
202
+ # end
203
+ #
204
+ def fieldset(&block)
205
+ @g.fieldset(block)
206
+ end
63
207
 
64
- def input_text(label, name, value = nil, args = {})
65
- id = id_for(name)
66
- args = args.merge(:type => :text, :name => name, :class => 'text', :id => id)
67
- args[:value] = value unless value.nil?
208
+ ##
209
+ # Generate an input tag with a type of "text" along with a label tag.
210
+ # This method also has the alias "text" so feel free to use that one instead of input_text.
211
+ #
212
+ # @param [String] label The text to display inside the label tag.
213
+ # @param [String Symbol] name The name of the text field.
214
+ # @param [Hash] args Any additional HTML attributes along with their values.
215
+ # @example
216
+ #
217
+ # form_for(@data, :method => :post) do |f|
218
+ # f.input_text 'Username', :username
219
+ # end
220
+ #
221
+ def input_text(label, name, args = {})
222
+ # The ID can come from 2 places, id_for and the args hash
223
+ id = args[:id] ? args[:id] : id_for(name)
224
+ args = args.merge(:type => :text, :name => name, :id => id)
225
+
226
+ if !args[:value] and @form_values.respond_to?(name)
227
+ args[:value] = @form_values.send(name)
228
+ end
68
229
 
69
230
  @g.p do
70
231
  label_for(id, label, name)
@@ -73,10 +234,29 @@ module Ramaze
73
234
  end
74
235
  alias text input_text
75
236
 
76
- def input_password(label, name)
77
- id = id_for(name)
78
- args = {:type => :password, :name => name, :class => 'text', :id => id}
79
-
237
+ ##
238
+ # Generate an input tag with a type of "password" along with a label.
239
+ # Password fields are pretty much the same as text fields except that the content of these fields is replaced with dots.
240
+ # This method has the following alias: "password".
241
+ #
242
+ # @param [String] label The text to display inside the label tag.
243
+ # @param [String Symbol] name The name of the password field.
244
+ # @param [Hash] args Any additional HTML attributes along with their values.
245
+ # @example
246
+ #
247
+ # form_for(@data, :method => :post) do |f|
248
+ # f.input_password 'My password', :password
249
+ # end
250
+ #
251
+ def input_password(label, name, args = {})
252
+ # The ID can come from 2 places, id_for and the args hash
253
+ id = args[:id] ? args[:id] : id_for(name)
254
+ args = args.merge(:type => :password, :name => name, :id => id)
255
+
256
+ if !args[:value] and @form_values.respond_to?(name)
257
+ args[:value] = @form_values.send(name)
258
+ end
259
+
80
260
  @g.p do
81
261
  label_for(id, label, name)
82
262
  @g.input(args)
@@ -84,8 +264,20 @@ module Ramaze
84
264
  end
85
265
  alias password input_password
86
266
 
87
- def input_submit(value = nil)
88
- args = {:type => :submit, :class => 'button submit'}
267
+ ##
268
+ # Generate a submit tag (without a label). A submit tag is a button that once it's clicked
269
+ # will send the form data to the server.
270
+ #
271
+ # @param [String] value The text to display in the button.
272
+ # @param [Hash] args Any additional HTML attributes along with their values.
273
+ # @example
274
+ #
275
+ # form_for(@data, :method => :post) do |f|
276
+ # f.input_submit 'Save'
277
+ # end
278
+ #
279
+ def input_submit(value = nil, args = {})
280
+ args = args.merge(:type => :submit)
89
281
  args[:value] = value unless value.nil?
90
282
 
91
283
  @g.p do
@@ -94,49 +286,193 @@ module Ramaze
94
286
  end
95
287
  alias submit input_submit
96
288
 
97
- def input_checkbox(label, name, checked = false)
98
- id = id_for(name)
99
- args = {:type => :checkbox, :name => name, :class => 'checkbox', :id => id}
100
- args[:checked] = 'checked' if checked
289
+ ##
290
+ # Generate an input tag with a type of "checkbox". This method will also
291
+ # generate a hidden field with the same name as the checkbox to ensure
292
+ # that the data is always submitted.
293
+ #
294
+ # If you want to have multiple checkboxes you can either use an array or a hash.
295
+ # In the case of an array the values will also be used as text for each checkbox.
296
+ # When using a hash the key will be displayed and the value will be the value of the
297
+ # checkbox. Example:
298
+ #
299
+ # @data = Class.new
300
+ # attr_reader :gender_arr
301
+ # attr_reader :gender_hash
302
+ #
303
+ # def initialize
304
+ # @gender_arr = ['male', 'female']
305
+ # @gender_hash = {"Male" => "male", "Female" => "female"}
306
+ # end
307
+ # end.new
308
+ #
309
+ # form_for(@data, :method => :post) do |f|
310
+ # f.input_checkbox "Gender", :gender_arr
311
+ # f.input_checkbox "Gender", :gender_hash
312
+ # end
313
+ #
314
+ # @param [String] label The text to display inside the label tag.
315
+ # @param [String Symbol] name The name of the checkbox.
316
+ # @param [String] checked String that indicates if (and which) checkbox should be checked.
317
+ # @param [Hash] args Any additional HTML attributes along with their values.
318
+ # @example
319
+ #
320
+ # form_for(@data, :method => :post) do |f|
321
+ # f.input_checkbox 'Remember me', :remember_user
322
+ # end
323
+ #
324
+ def input_checkbox(label, name, checked = nil, args = {})
325
+ id = args[:id] ? args[:id] : "#{id_for(name)}_0"
326
+
327
+ # Get the default value for the checkbox used for the hidden field.
328
+ if args[:default]
329
+ default = args[:default]
330
+ args.delete(:default)
331
+ else
332
+ default = 0
333
+ end
101
334
 
102
- @g.p do
103
- label_for(id, label, name)
104
- @g.input(args)
335
+ # Get the checkbox value from either the args hash or from
336
+ # the form object (as specified in the form_for() method).
337
+ if !args[:values] and @form_values.respond_to?(name)
338
+ args[:values] = @form_values.send(name)
339
+ end
340
+
341
+ # That class for each element wrapper (a span tag) can be customized
342
+ # using :span_class => "a_class".
343
+ if args[:span_class]
344
+ span_class = args[:span_class]
345
+ args.delete(:span_class)
346
+ else
347
+ span_class = "checkbox_wrap"
348
+ end
349
+
350
+ # Get the type from the args hash instead of pre-defining it. Doing so means we can use
351
+ # this method for the input_radio method.
352
+ if !args[:type]
353
+ args[:type] = :checkbox
354
+ end
355
+
356
+ # Convert the values to an array if it's something we can't use in a loop (e.g. a string).
357
+ if args[:values].class != Hash and args[:values].class != Array
358
+ args[:values] = [args[:values]]
359
+ end
360
+
361
+ # Create a checkbox for each value
362
+ if !args[:values].empty?
363
+ @g.p do
364
+ # Let's create the label and the hidden field
365
+ label_for(id, label, name)
366
+ self.input_hidden(name, default)
367
+
368
+ # Loop through all the values. Each checkbox will have an ID of "form-NAME-INDEX".
369
+ # Each name will be NAME followed by [] to indicate it's an array (since multiple values are possible).
370
+ args[:values].each_with_index do |value, index|
371
+ id = args[:id] ? args[:id] : "#{id_for(name)}_#{index}"
372
+
373
+ if args[:type] == :checkbox
374
+ checkbox_name = "#{name}[]"
375
+ else
376
+ checkbox_name = name
377
+ end
378
+
379
+ # Copy all additional attributes and their values except the values array.
380
+ opts = args.clone
381
+ opts.delete(:values)
382
+
383
+ # Get the value and text to display for each checkbox
384
+ if value.class == Array
385
+ checkbox_text = value[0]
386
+ checkbox_value = value[1]
387
+ else
388
+ checkbox_text = checkbox_value = value
389
+ end
390
+
391
+ # Let's see if the current item is checked
392
+ if checkbox_value == checked
393
+ opts[:checked] = 'checked'
394
+ end
395
+
396
+ # And we're done, easy wasn't it?
397
+ opts = opts.merge(:name => checkbox_name, :id => id, :value => checkbox_value)
398
+
399
+ # Generate the following HTML:
400
+ #
401
+ # <span class="#{span_class}">
402
+ # <input type="checkbox" name="#{checkbox_name}" id="#{id}" value="#{value}" /> #{value}
403
+ # </span>
404
+ #
405
+ @g.span :class => span_class do
406
+ @g.input(opts)
407
+ " #{checkbox_text}"
408
+ end
409
+ end
410
+ end
105
411
  end
106
412
  end
107
413
  alias checkbox input_checkbox
108
414
 
109
- def input_radio(label, name, values, options = {})
110
- has_checked, checked = options.key?(:checked), options[:checked]
111
-
112
- @g.p do
113
- values.each_with_index do |(value, o_name), index|
114
- o_name ||= value
115
- id = id_for("#{name}-#{index}")
116
-
117
- o_args = {:type => :radio, :value => value, :id => id, :name => name}
118
- o_args[:checked] = 'checked' if has_checked && value == checked
119
-
120
- if error = @form_errors.delete(name.to_s)
121
- @g.label(:for => id){
122
- @g.span(:class => :error){ error }
123
- @g.input(o_args)
124
- @g.out << o_name
125
- }
126
- else
127
- @g.label(:for => id){
128
- @g.input(o_args)
129
- @g.out << o_name
130
- }
131
- end
132
- end
415
+ ##
416
+ # Generate an input tag with a type of "radio". This method will also
417
+ # generate a hidden field with the same name as the radio button to ensure
418
+ # that the data is always submitted.
419
+ #
420
+ # If you want to generate multiple radio buttons you can use an array just like
421
+ # you can with checkboxes. Example:
422
+ #
423
+ # @data = Class.new
424
+ # attr_reader :gender_arr
425
+ # attr_reader :gender_hash
426
+ #
427
+ # def initialize
428
+ # @gender_arr = ['male', 'female']
429
+ # @gender_hash = {"Male" => "male", "Female" => "female"}
430
+ # end
431
+ # end.new
432
+ #
433
+ # form_for(@data, :method => :post) do |f|
434
+ # f.input_radio "Gender", :gender_arr
435
+ # f.input_radio "Gender", :gender_hash
436
+ # end
437
+ #
438
+ # @param [String] label The text to display inside the label tag.
439
+ # @param [String Symbol] name The name of the radio button.
440
+ # @param [String] checked String that indicates if (and which) radio button should be checked.
441
+ # @param [Hash] args Any additional HTML attributes along with their values.
442
+ # @see input_checkbox()
443
+ # @example
444
+ #
445
+ # form_for(@data, :method => :post) do |f|
446
+ # f.input_radio 'Gender', :gender
447
+ # end
448
+ #
449
+ def input_radio(label, name, checked = nil, args = {})
450
+ # Force a type of "radio"
451
+ args[:type] = :radio
452
+
453
+ if !args[:span_class]
454
+ args[:span_class] = "radio_wrap"
133
455
  end
456
+
457
+ self.input_checkbox(label, name, checked, args)
134
458
  end
135
459
  alias radio input_radio
136
460
 
137
- def input_file(label, name)
138
- id = id_for(name)
139
- args = {:type => :file, :name => name, :class => 'file', :id => id}
461
+ ##
462
+ # Generate a field for uploading files.
463
+ #
464
+ # @param [String] label The text to display inside the label tag.
465
+ # @param [String Symbol] name The name of the radio tag.
466
+ # @param [Hash] args Any additional HTML attributes along with their values.
467
+ # @example
468
+ #
469
+ # form_for(@data, :method => :post) do |f|
470
+ # f.input_file 'Image', :image
471
+ # end
472
+ #
473
+ def input_file(label, name, args = {})
474
+ id = args[:id] ? args[:id] : id_for(name)
475
+ args = args.merge(:type => :file, :name => name, :id => id)
140
476
 
141
477
  @g.p do
142
478
  label_for(id, label, name)
@@ -145,17 +481,56 @@ module Ramaze
145
481
  end
146
482
  alias file input_file
147
483
 
148
- def input_hidden(name, value = nil)
149
- args = {:type => :hidden, :name => name}
150
- args[:value] = value.to_s unless value.nil?
484
+ ##
485
+ # Generate a hidden field. Hidden fields are essentially the same as text fields
486
+ # except that they aren't displayed in the browser.
487
+ #
488
+ # @param [String Symbol] name The name of the hidden field tag.
489
+ # @param [String] value The value of the hidden field
490
+ # @param [Hash] args Any additional HTML attributes along with their values.
491
+ # @example
492
+ #
493
+ # form_for(@data, :method => :post) do |f|
494
+ # f.input_hidden :user_id
495
+ # end
496
+ #
497
+ def input_hidden(name, value = nil, args = {})
498
+ args = args.merge(:type => :hidden, :name => name)
499
+
500
+ if !value and @form_values.respond_to?(name)
501
+ args[:value] = @form_values.send(name)
502
+ else
503
+ args[:value] = value
504
+ end
151
505
 
152
506
  @g.input(args)
153
507
  end
154
508
  alias hidden input_hidden
155
509
 
156
- def textarea(label, name, value = nil)
157
- id = id_for(name)
158
- args = {:name => name, :id => id}
510
+ ##
511
+ # Generate a text area.
512
+ #
513
+ # @param [String] label The text to display inside the label tag.
514
+ # @param [String Symbol] name The name of the textarea.
515
+ # @param [Hash] args Any additional HTML attributes along with their values.
516
+ # @example
517
+ #
518
+ # form_for(@data, :method => :post) do |f|
519
+ # f.textarea 'Description', :description
520
+ # end
521
+ #
522
+ def textarea(label, name, args = {})
523
+ id = args[:id] ? args[:id] : id_for(name)
524
+
525
+ # Get the value of the textarea
526
+ if !args[:value] and @form_values.respond_to?(name)
527
+ value = @form_values.send(name)
528
+ else
529
+ value = args[:value]
530
+ args.delete(:value)
531
+ end
532
+
533
+ args = args.merge(:name => name, :id => id)
159
534
 
160
535
  @g.p do
161
536
  label_for(id, label, name)
@@ -163,16 +538,39 @@ module Ramaze
163
538
  end
164
539
  end
165
540
 
166
- def select(label, name, values, options = {})
167
- id = id_for(name)
168
- multiple, size = options.values_at(:multiple, :size)
169
-
170
- args = {:id => id}
541
+ ##
542
+ # Generate a select tag along with the option tags and a label.
543
+ #
544
+ # @param [String] label The text to display inside the label tag.
545
+ # @param [String Symbol] name The name of the select tag.
546
+ # @param [Hash] args Hash containing additional HTML attributes.
547
+ # @example
548
+ #
549
+ # form_for(@data, :method => :post) do |f|
550
+ # f.select 'Country', :country_list
551
+ # end
552
+ #
553
+ def select(label, name, args = {})
554
+ id = args[:id] ? args[:id] : id_for(name)
555
+ multiple, size = args.values_at(:multiple, :size)
556
+
557
+ # Get all the values
558
+ if !args[:values] and @form_values.respond_to?(name)
559
+ values = @form_values.send(name)
560
+ else
561
+ values = args[:values]
562
+ args.delete(:values)
563
+ end
564
+
171
565
  args[:multiple] = 'multiple' if multiple
172
- args[:size] = (size || multiple || 1).to_i
173
- args[:name] = multiple ? "#{name}[]" : name
566
+ args[:size] = (size || values.count || 1).to_i
567
+ args[:name] = multiple ? "#{name}[]" : name
568
+ args = args.merge(:id => id)
174
569
 
175
- has_selected, selected = options.key?(:selected), options[:selected]
570
+ # Retrieve the selected value
571
+ has_selected, selected = args.key?(:selected), args[:selected]
572
+ selected = [selected] if !selected.is_a?(Array)
573
+ args.delete(:selected)
176
574
 
177
575
  @g.p do
178
576
  label_for(id, label, name)
@@ -180,19 +578,35 @@ module Ramaze
180
578
  values.each do |value, o_name|
181
579
  o_name ||= value
182
580
  o_args = {:value => value}
183
- o_args[:selected] = 'selected' if has_selected && value == selected
581
+
582
+ if has_selected and selected.include?(value)
583
+ o_args[:selected] = 'selected'
584
+ end
585
+
184
586
  @g.option(o_args){ o_name }
185
587
  end
186
588
  end
187
589
  end
188
590
  end
189
591
 
592
+ ##
593
+ # Method used for converting the results of the BlueForm helper to a string
594
+ #
595
+ # @return [String] The form output
596
+ #
190
597
  def to_s
191
598
  @g.to_s
192
599
  end
193
600
 
194
601
  private
195
602
 
603
+ ##
604
+ # Generate a label based on the id and value.
605
+ #
606
+ # @param [String] id The ID to which the label belongs.
607
+ # @param [String] value The text to display inside the label tag.
608
+ # @param [String] name The name of the field to which the label belongs.
609
+ #
196
610
  def label_for(id, value, name)
197
611
  if error = @form_errors.delete(name.to_s)
198
612
  @g.label("#{value} ", :for => id){ @g.span(:class => :error){ error } }
@@ -201,11 +615,17 @@ module Ramaze
201
615
  end
202
616
  end
203
617
 
618
+ ##
619
+ # Generate a value for an ID tag based on the field's name.
620
+ #
621
+ # @param [String] field_name The name of the field.
622
+ # @return [String] The ID for the specified field name.
623
+ #
204
624
  def id_for(field_name)
205
625
  if name = @form_args[:name]
206
- "#{name}-#{field_name}".downcase.gsub(/_/, '-')
626
+ "#{name}_#{field_name}".downcase.gsub(/-/, '_')
207
627
  else
208
- "form-#{field_name}".downcase.gsub(/_/, '-')
628
+ "form_#{field_name}".downcase.gsub(/-/, '_')
209
629
  end
210
630
  end
211
631
  end