nice-n-easy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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