nice-n-easy 1.0.0

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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ docs
20
+ pkg
21
+
22
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Brian Landau
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,49 @@
1
+ # Nice 'n' Easy
2
+
3
+ > "Lets take it nice and easy
4
+ > Its gonna be so easy
5
+ > ...
6
+ > cause nice and easy does it every time"
7
+
8
+ A set of Sinatra HTML view helpers to make your life nicer and easier.
9
+ It includes helpers for form fields, links, and assets.
10
+
11
+ In designing the library I tried to limit my use of external libs as much as possible.
12
+ In particular, I found a number of similar Sinatra HTML helpers but a number included all of
13
+ ActiveSupport.
14
+ ActiveSupport's "blank" functionality is included here, and I copy "`extract_options!`" and
15
+ "`symbolize_keys`" from there too but I add nothing else. Although if you include
16
+ ActiveSupport yourself the `label` helper will take advantage of the `titleize` method.
17
+
18
+
19
+ ## Install & Usage
20
+
21
+ Install:
22
+
23
+ sudo gem install nice-n-easy
24
+
25
+ Add this to your Sinatra app:
26
+
27
+ require 'sinatra/nice_easy_helpers'
28
+
29
+ class Main < Sinatra::Base
30
+ helpers Sinatra::NiceEasyHelpers
31
+ end
32
+
33
+ See the RDocs for how to use the individual helpers
34
+
35
+
36
+ ## Note on Patches/Pull Requests
37
+
38
+ * Fork the project.
39
+ * Make your feature addition or bug fix.
40
+ * Add tests for it. This is important so I don't break it in a
41
+ future version unintentionally.
42
+ * Commit, do not mess with rakefile, version, or history.
43
+ (if you want to have your own version, that is fine but
44
+ bump version in a commit by itself I can ignore when I pull)
45
+ * Send me a pull request. Bonus points for topic branches.
46
+
47
+ ## Copyright
48
+
49
+ Copyright (c) 2009 Brian Landau. See LICENSE for details.
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "nice-n-easy"
8
+ gem.summary = %Q{Sinatra HTML helpers that are nice-n-easy to use.}
9
+ gem.description = %Q{A set of Sinatra HTML view helpers to make your life nicer and easier. Helpers for forms, links, and assets.}
10
+ gem.email = "brian.landau@viget.com"
11
+ gem.homepage = "http://github.com/brianjlandau/nice-n-easy"
12
+ gem.authors = ["Brian Landau"]
13
+ gem.add_dependency('sinatra', '~> 0.9')
14
+ gem.add_dependency('activesupport', '~> 2.3')
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'tasks/test'
23
+ require 'tasks/rdoc'
24
+
25
+ task :test => :check_dependencies
26
+ task :default => :test
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,563 @@
1
+ require 'sinatra/base'
2
+ unless Object.method_defined?(:blank?)
3
+ require 'active_support/core_ext/blank'
4
+ end
5
+
6
+ module Sinatra #:nodoc:
7
+ module NiceEasyHelpers
8
+ # :stopdoc:
9
+ BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked selected).to_set
10
+ BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
11
+ HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;' }
12
+ # :startdoc:
13
+
14
+ # Creates a link to a given URL with the given text as the link.
15
+ # link "Check this out", '/path/to/something' # =>
16
+ # <a href="/path/to/something">Check this out</a>
17
+ #
18
+ def link(content, href, options = {})
19
+ tag :a, content, options.merge(:href => href)
20
+ end
21
+
22
+ # Creates an image tag for the given image file.
23
+ # If a relative source is given it assumes it lives in <tt>/images/</tt> on your server.
24
+ # image_tag "button.jpg" :alt => 'Do some Action' # =>
25
+ # <img src="/images/button.jpg" alt="Do some Action" />
26
+ #
27
+ # image_tag "/icons/delete.jpg" :alt => 'Remove', :class => 'small-button' # =>
28
+ # <img src="/icons/delete.jpg" alt="Remove" class="small-button" />
29
+ #
30
+ # image_tag "http://www.example.com/close.jpg" # =>
31
+ # <img src="http://www.example.com/close.jpg" />
32
+ #
33
+ def image_tag(src, options = {})
34
+ single_tag :img, options.merge(:src => compute_public_path(src, 'images'))
35
+ end
36
+
37
+ # Creates a script tag for each source provided. If you just supply a relative filename
38
+ # (with or without the .js extension) it will assume it can be found in your public/javascripts
39
+ # directory. If you provide an absolute path it will use that.
40
+ #
41
+ # javascript_include_tag 'jquery' # =>
42
+ # <script src="/javascripts/jquery.js" type="text/javascript"></script>
43
+ #
44
+ # javascript_include_tag 'jquery', 'jquery-ui.min.js' # =>
45
+ # <script src="/javascripts/jquery.js" type="text/javascript"></script>
46
+ # <script src="/javascripts/jquery-ui.min.js" type="text/javascript"></script>
47
+ #
48
+ # javascript_include_tag '/js/facebox.js' # =>
49
+ # <script src="/js/facebox.js" type="text/javascript"></script>
50
+ #
51
+ # javascript_include_tag 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js' # =>
52
+ # <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
53
+ #
54
+ def javascript_include_tag(*sources)
55
+ sources.inject([]) { |tags, source|
56
+ tags << tag(:script, '', {:src => compute_public_path(source, 'javascripts', 'js'), :type => 'text/javascript'})
57
+ tags
58
+ }.join("\n")
59
+ end
60
+
61
+ # :call-seq:
62
+ # stylesheet_link_tag(*sources, options = {})
63
+ #
64
+ # This helper is used to create a set of link tags for CSS stylesheets. If you just
65
+ # supply a relative filename (with or without the .css extension) it will assume it
66
+ # can be found in your public/javascripts directory. If you provide
67
+ # an absolute path it will use that. You can also pass attributes for the link tag
68
+ # in a hash as the last argument.
69
+ #
70
+ # stylesheet_link_tag 'global' # =>
71
+ # <link href="/stylesheets/global.css" rel="stylesheet" type="text/css" media="screen" />
72
+ #
73
+ # stylesheet_link_tag 'global' # =>
74
+ # <link href="/stylesheets/global.css" rel="stylesheet" type="text/css" media="screen" />
75
+ #
76
+ def stylesheet_link_tag(*sources)
77
+ options = sources.extract_options!.symbolize_keys
78
+ sources.inject([]) { |tags, source|
79
+ tags << single_tag(:link, {:href => compute_public_path(source, 'stylesheets', 'css'),
80
+ :type => 'text/css', :rel => 'stylesheet', :media => 'screen'}.merge(options))
81
+ tags
82
+ }.join("\n")
83
+ end
84
+
85
+ # :call-seq:
86
+ # label(field, options = {})
87
+ # label(obj, field, options = {})
88
+ #
89
+ # Creates a label tag for the specified field, which may be a field on an object.
90
+ # It will use the field name as the text for the label unless a <tt>:text</tt>
91
+ # option is provided.
92
+ #
93
+ # label :email # =>
94
+ # <label for="email">Email</label>
95
+ #
96
+ # label :email, :text => "Email Address:" # =>
97
+ # <label for="email">Email Address:</label>
98
+ #
99
+ # label :user, :email # =>
100
+ # <label for="user_email">Email</label>
101
+ #
102
+ def label(*args)
103
+ obj, field, options = extract_options_and_field(*args)
104
+ text = options.delete(:text)
105
+ if text.blank?
106
+ if String.method_defined?(:titleize)
107
+ text = field.blank? ? obj.to_s.titleize : field.to_s.titleize
108
+ else
109
+ text = field.blank? ? obj.to_s : field.to_s
110
+ end
111
+ end
112
+ tag :label, text, options.merge(:for => (field.blank? ? obj : "#{obj}_#{field}"))
113
+ end
114
+
115
+ # :call-seq:
116
+ # text_field(field, options = {})
117
+ # text_field(obj, field, options = {})
118
+ #
119
+ # Returns a text input for the specified field, which may be on an object.
120
+ # If there is a value for the field in the request params or an object is
121
+ # provided with that value set, it will auto-populate the input.
122
+ #
123
+ # text_field :email # =>
124
+ # <input type="text" name="email" id="email" />
125
+ #
126
+ # # Where the @params[:email] value is set already to "joe@example.com"
127
+ # text_field :email # =>
128
+ # <input type="text" name="email" id="email" value="joe@example.com" />
129
+ #
130
+ # text_field :user, :email # =>
131
+ # <input type="text" name="user[email]" id="user_email" />
132
+ #
133
+ # @user = User.new(:email => 'joe@example.com')
134
+ # text_field @user, :email # =>
135
+ # <input type="text" name="user[email]" id="user_email" value="joe@example.com" />
136
+ #
137
+ def text_field(*args)
138
+ input_tag 'text', *args
139
+ end
140
+
141
+ # :call-seq:
142
+ # password_field(field, options = {})
143
+ # password_field(obj, field, options = {})
144
+ #
145
+ # Returns a password input for the specified field, which may be on an object.
146
+ # If there is a value for the field in the request params or an object is
147
+ # provided with that value set, it will auto-populate the input.
148
+ #
149
+ # password_field :password # =>
150
+ # <input type="password" name="password" id="password" />
151
+ #
152
+ # # Where the @params[:password] value is set already to "secret"
153
+ # password_field :password # =>
154
+ # <input type="password" name="password" id="password" value="secret" />
155
+ #
156
+ # password_field :user, :password # =>
157
+ # <input type="password" name="user[password]" id="user_password" />
158
+ #
159
+ # @user = User.new(:password => 'secret')
160
+ # password_field @user, :password # =>
161
+ # <input type="password" name="user[password]" id="user_password" value="secret" />
162
+ #
163
+ def password_field(*args)
164
+ input_tag 'password', *args
165
+ end
166
+
167
+ # :call-seq:
168
+ # file_field(field, options = {})
169
+ # file_field(obj, field, options = {})
170
+ #
171
+ # Returns a file input for the specified field, which may be on an object.
172
+ #
173
+ # file_field :picture # =>
174
+ # <input type="file" name="picture" id="picture" />
175
+ #
176
+ # file_field :user, :picture # =>
177
+ # <input type="file" name="user[picture]" id="user_picture" />
178
+ #
179
+ # @user = User.new
180
+ # file_field @user, :picture # =>
181
+ # <input type="file" name="user[picture]" id="user_picture" />
182
+ #
183
+ def file_field(*args)
184
+ input_tag 'file', *args
185
+ end
186
+
187
+ # :call-seq:
188
+ # button(name, content, options = {})
189
+ # button(name, content, type = "submit", options = {})
190
+ #
191
+ # Creates a button element with the name/id and with the content provided.
192
+ # Defaults to the submit type.
193
+ #
194
+ # button "continue", "Save and continue" # =>
195
+ # <button id="continue" name="continue" type="submit">Save and continue</button>
196
+ #
197
+ # button "add-email", "Add another Email", 'button' # =>
198
+ # <button id="add-email" name="add-email" type="button">Add another Email</button>
199
+ #
200
+ def button(*args)
201
+ options = args.extract_options!.symbolize_keys
202
+ name = args.shift
203
+ content = args.shift
204
+ type = args.shift || 'submit'
205
+ tag :button, content, options.merge(:type => type, :name => name, :id => name)
206
+ end
207
+
208
+ # :call-seq:
209
+ # text_area(field, options = {})
210
+ # text_area(obj, field, options = {})
211
+ #
212
+ # Returns a text area tag for the specified field, which may be on an object.
213
+ # If there is a value for the field in the request params or an object is
214
+ # provided with that value set, it will auto-populate the input.
215
+ #
216
+ # text_area :description # =>
217
+ # <textarea name="description" id="description"></textarea>
218
+ #
219
+ # # Where the @params[:description] value is set already to "A brand new book."
220
+ # text_area :description # =>
221
+ # <textarea name="description" id="description">A brand new book.</textarea>
222
+ #
223
+ # text_area :product, :description # =>
224
+ # <textarea name="product[description]" id="product_description"></textarea>
225
+ #
226
+ # @product = Product.new(:description => 'A brand new book.')
227
+ # text_area @product, :description # =>
228
+ # <textarea name="product[description]" id="product_description"></textarea>
229
+ #
230
+ def text_area(*args)
231
+ obj, field, options = extract_options_and_field(*args)
232
+ value = get_value(obj, field)
233
+ tag :textarea, value, options.merge(get_id_and_name(obj, field))
234
+ end
235
+
236
+ # Creates an image input field for the source and options provided.
237
+ # The image source is calculated the same way it is for Sinatra::NiceEasyHelpers#image_tag
238
+ #
239
+ # image_input "buttons/save_close.png", :alt => 'Save and close'
240
+ # <input type="image" src="buttons/save_close.png" alt="Save and close" />
241
+ def image_input(src, options = {})
242
+ single_tag :input, options.merge(:type => 'image', :src => compute_public_path(src, 'images'))
243
+ end
244
+
245
+ # Creates as submit input field with the text and options provided.
246
+ #
247
+ # submit # =>
248
+ # <input type="submit" value="Save" />
249
+ #
250
+ # submit 'Save and continue' # =>
251
+ # <input type="submit" value="Save and continue" />
252
+ #
253
+ def submit(value = "Save", options = {})
254
+ single_tag :input, options.merge(:type => "submit", :value => value)
255
+ end
256
+
257
+ # :call-seq:
258
+ # checkbox_field(field, options)
259
+ # checkbox_field(obj, field, options)
260
+ #
261
+ # Returns a checkbox input for the specified field, which may be on an object.
262
+ # If there is a value for the field in the request params or an object is
263
+ # provided with that value set, it will mark this field as checked if it
264
+ # matches the value for the field.
265
+ #
266
+ # checkbox_field :subscriptions, :value => 1 # =>
267
+ # <input type="checkbox" name="subscriptions" id="subscriptions" value="1" />
268
+ #
269
+ # # Where the @params[:subscriptions] value is set already to "1"
270
+ # checkbox_field :subscriptions, :value => 1 # =>
271
+ # <input type="checkbox" name="subscriptions" id="subscriptions" value="1" checked="checked" />
272
+ #
273
+ # checkbox_field :user, :subscriptions, :value => 1 # =>
274
+ # <input type="checkbox" name="user[subscriptions]" id="user_subscriptions" value="1" />
275
+ #
276
+ # @user = User.new(:subscriptions => ['newsletters'])
277
+ # checkbox_field @user, :subscriptions, :value => 'newsletters' # =>
278
+ # <input type="checkbox" name="user[subscriptions]" id="user_subscriptions" value="newsletters" checked="checked" />
279
+ #
280
+ def checkbox_field(*args)
281
+ input_tag 'checkbox', *args
282
+ end
283
+
284
+ # :call-seq:
285
+ # radio_button(field, options)
286
+ # radio_button(obj, field, options)
287
+ #
288
+ # Returns a radio button input for the specified field, which may be on an object.
289
+ # If there is a value for the field in the request params or an object is
290
+ # provided with that value set, it will mark this field as checked if it
291
+ # matches the value for the field.
292
+ #
293
+ # radio_button :education, :value => 'college' # =>
294
+ # <input type="radio" name="education" id="education" value="college" />
295
+ #
296
+ # # Where the @params[:education] value is set already to "college"
297
+ # radio_button :education, :value => 'college # =>
298
+ # <input type="radio" name="education" id="education" value="college" checked="checked" />
299
+ #
300
+ # radio_button :user, :education, :value => 'college' # =>
301
+ # <input type="radio" name="user[education]" id="user_education" value="college" />
302
+ #
303
+ # @user = User.new(:education => 'college')
304
+ # radio_button @user, :education, :value => 'college' # =>
305
+ # <input type="radio" name="user[education]" id="user_education" value="college" checked="checked" />
306
+ #
307
+ def radio_button(*args)
308
+ input_tag 'radio', *args
309
+ end
310
+
311
+ # :call-seq:
312
+ # select_field(field, choices, options = {})
313
+ # select_field(obj, field, choices, options = {})
314
+ #
315
+ # Creates a select tag for the specified field, which may be on an object.
316
+ # The helper also creates the options elements inside the select tag for each
317
+ # of the choices provided.
318
+ #
319
+ # Given a choices container of an array of strings the strings will be used for
320
+ # the test and value of the options.
321
+ # Given a container where the elements respond to first and last (such as a two-element array),
322
+ # the “lasts” serve as option values and the “firsts” as option text.
323
+ # Hashes are turned into this form automatically, so the keys become “firsts” and values become lasts.
324
+ #
325
+ # If there is a value for the field in the request params or an object is
326
+ # provided with that value set, it will auto-select that options from the choices.
327
+ #
328
+ # select_field :membership_type, ['Lifetime', '1 Month', '1 Year'] # =>
329
+ # <select name="membership_type" id="membership_type">
330
+ # <option value="Lifetime">Lifetime</option>
331
+ # <option value="1 Month">1 Month</option>
332
+ # <option value="1 Year">1 Year</option>
333
+ # </select>
334
+ #
335
+ # select_field :membership_type, [['Lifetime', 1], ['1 Month', 2], ['1 Year', 3]] # =>
336
+ # <select name="membership_type" id="membership_type">
337
+ # <option value="1">Lifetime</option>
338
+ # <option value="2">1 Month</option>
339
+ # <option value="3">1 Year</option>
340
+ # </select>
341
+ #
342
+ # select_field :membership_type, {'Lifetime' => 1, '1 Month' => 2, '1 Year' => 3} # =>
343
+ # <select name="membership_type" id="membership_type">
344
+ # <option value="1">Lifetime</option>
345
+ # <option value="2">1 Month</option>
346
+ # <option value="3">1 Year</option>
347
+ # </select>
348
+ #
349
+ # # Where the @params[:membership_type] value is set already to "year"
350
+ # select_field :membership_type, {'Lifetime' => 'life', '1 Month' => 'month', '1 Year' => 'year'} # =>
351
+ # <select name="membership_type" id="membership_type">
352
+ # <option value="life">Lifetime</option>
353
+ # <option value="month">1 Month</option>
354
+ # <option value="year" selected="selected">1 Year</option>
355
+ # </select>
356
+ #
357
+ # select_field :user, :membership_type, ['Lifetime', '1 Month', '1 Year'] # =>
358
+ # <select name="user[membership_type]" id="user_membership_type">
359
+ # <option value="Lifetime">Lifetime</option>
360
+ # <option value="1 Month">1 Month</option>
361
+ # <option value="1 Year">1 Year</option>
362
+ # </select>
363
+ #
364
+ # @user = User.new(:membership_type => 'month')
365
+ # select_field :user, :membership_type, {'Lifetime' => 'life', '1 Month' => 'month', '1 Year' => 'year'} # =>
366
+ # <select name="user[membership_type]" id="user_membership_type">
367
+ # <option value="life">Lifetime</option>
368
+ # <option value="month" selected="selected">1 Month</option>
369
+ # <option value="year">1 Year</option>
370
+ # </select>
371
+ #
372
+ def select_field(*args)
373
+ case args.size
374
+ when 2
375
+ options = {}
376
+ choices = args.pop
377
+ obj = args.shift
378
+ field = nil
379
+ else
380
+ options = args.extract_options!.symbolize_keys
381
+ choices = args.pop
382
+ obj = args.shift
383
+ field = args.shift
384
+ end
385
+
386
+ unless choices.is_a? Enumerable
387
+ raise ArgumentError, 'the choices parameter must be an Enumerable object'
388
+ end
389
+
390
+ value = get_value(obj, field)
391
+
392
+ content = choices.inject([]) { |opts, choice|
393
+ text, opt_val = option_text_and_value(choice)
394
+ opts << tag(:option, escape_once(text), {:value => escape_once(opt_val), :selected => (opt_val == value)})
395
+ }.join("\n")
396
+
397
+ tag :select, "\n#{content}\n", options.merge(get_id_and_name(obj, field))
398
+ end
399
+
400
+ # :call-seq:
401
+ # hidden_field(field, options = {})
402
+ # hidden_field(obj, field, options = {})
403
+ #
404
+ # Returns a hidden input for the specified field, which may be on an object.
405
+ # If there is a value for the field in the request params or an object is
406
+ # provided with that value set, it will auto-populate the input. If not
407
+ # you should set the value with the <tt>:value</tt> option.
408
+ #
409
+ # hidden_field :external_id, :value => 25 # =>
410
+ # <input type="hidden" name="external_id" id="external_id" value="25" />
411
+ #
412
+ # # Where the @params[:external_id] value is set already to "247"
413
+ # hidden_field :external_id # =>
414
+ # <input type="hidden" name="external_id" id="external_id" value="247" />
415
+ #
416
+ # hidden_field :user, :external_id, :value => 25 # =>
417
+ # <input type="hidden" name="user[external_id]" id="user_external_id" value="25" />
418
+ #
419
+ # @user = User.new(:external_id => 247)
420
+ # hidden_field @user, :external_id # =>
421
+ # <input type="hidden" name="user[external_id]" id="user_external_id" value="247" />
422
+ #
423
+ def hidden_field(*args)
424
+ input_tag 'hidden', *args
425
+ end
426
+
427
+ # Creats a standard open and close tags for the name provided with the content
428
+ # and attributes supplied.
429
+ # tag :h2, "Sinatra Steps to the Stage", :title => "Applause" # =>
430
+ # <h1 title="Applause">Sinatra Steps to the Stage</h1>
431
+ #
432
+ def tag(name, content, options = {})
433
+ "<#{name.to_s}#{tag_options(options)}>#{content}</#{name.to_s}>"
434
+ end
435
+
436
+ # Creates a self-closing/empty-element tag of the name specified.
437
+ # single_tag :img, :src => "/images/face.jpg" # =>
438
+ # <img src="/images/face.jpg" />
439
+ #
440
+ def single_tag(name, options = {})
441
+ "<#{name.to_s}#{tag_options(options)} />"
442
+ end
443
+
444
+ private
445
+
446
+ def escape_once(html) #:nodoc:
447
+ html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| HTML_ESCAPE[special] }
448
+ end
449
+
450
+ def input_tag(type, *args)
451
+ obj, field, options = extract_options_and_field(*args)
452
+ value = get_value(obj, field)
453
+ case type.to_sym
454
+ when :radio
455
+ if options[:value].nil? || options[:value] =~ /^\s*$/
456
+ raise ArgumentError, 'for radio inputs a value options must be provided'
457
+ end
458
+ options[:checked] = true if value == options[:value]
459
+ when :checkbox
460
+ if options[:value].nil? || options[:value] =~ /^\s*$/
461
+ raise ArgumentError, 'for checkbox inputs a value options must be provided'
462
+ end
463
+ options[:checked] = "checked" if (value == options[:value] || (value.is_a?(Enumerable) && value.include?(options[:value])))
464
+ else
465
+ options[:value] = value unless value.blank?
466
+ end
467
+ single_tag :input, options.merge(:type => type).merge(get_id_and_name(obj, field))
468
+ end
469
+
470
+ def get_value(obj, field)
471
+ if field.blank?
472
+ @params[obj]
473
+ else
474
+ case obj
475
+ when String, Symbol
476
+ begin
477
+ @params[obj][field]
478
+ rescue NoMethodError
479
+ nil
480
+ end
481
+ else
482
+ obj.send(field.to_sym)
483
+ end
484
+ end
485
+ end
486
+
487
+ def get_id_and_name(obj, field)
488
+ if field.blank?
489
+ {:id => obj.to_s, :name => obj.to_s}
490
+ else
491
+ case obj
492
+ when String, Symbol
493
+ {:id => "#{obj}_#{field}", :name => "#{obj}[#{field}]"}
494
+ else
495
+ obj_name = obj.class.name.demodulize.underscore
496
+ {:id => "#{obj_name}_#{field}", :name => "#{obj_name}[#{field}]"}
497
+ end
498
+ end
499
+ end
500
+
501
+ def option_text_and_value(option) #:nodoc:
502
+ # Options are [text, value] pairs or strings used for both.
503
+ if !option.is_a?(String) and option.respond_to?(:first) and option.respond_to?(:last)
504
+ [option.first, option.last]
505
+ else
506
+ [option, option]
507
+ end
508
+ end
509
+
510
+ def tag_options(options) #:nodoc:
511
+ unless options.blank?
512
+ attrs = []
513
+ options.each_pair do |key, value|
514
+ if BOOLEAN_ATTRIBUTES.include?(key)
515
+ attrs << %(#{key}="#{key}") if value
516
+ else
517
+ attrs << %(#{key}="#{escape_once(value)}") if !value.nil?
518
+ end
519
+ end
520
+ " #{attrs.sort * ' '}" unless attrs.empty?
521
+ end
522
+ end
523
+
524
+ def extract_options_and_field(*args) #:nodoc:
525
+ options = args.extract_options!.symbolize_keys
526
+ obj = args.shift
527
+ field = args.shift
528
+ [obj, field, options]
529
+ end
530
+
531
+ def compute_public_path(source, dir, ext = nil) #:nodoc:
532
+ source_ext = File.extname(source)[1..-1]
533
+ if ext && source_ext.blank?
534
+ source += ".#{ext}"
535
+ end
536
+
537
+ unless source =~ %r{^[-a-z]+://}
538
+ source = "/#{dir}/#{source}" unless source[0] == ?/
539
+ end
540
+
541
+ return source
542
+ end
543
+
544
+ end
545
+ end
546
+
547
+ unless Array.method_defined?(:extract_options!)
548
+ class Array #:nodoc:
549
+ def extract_options!
550
+ last.is_a?(::Hash) ? pop : {}
551
+ end
552
+ end
553
+ end
554
+ unless Hash.method_defined?(:symbolize_keys)
555
+ class Hash #:nodoc:
556
+ def symbolize_keys
557
+ inject({}) do |options, (key, value)|
558
+ options[(key.to_sym rescue key) || key] = value
559
+ options
560
+ end
561
+ end
562
+ end
563
+ end