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.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.md +49 -0
- data/Rakefile +26 -0
- data/VERSION +1 -0
- data/lib/sinatra/nice_easy_helpers.rb +563 -0
- data/nice-n-easy.gemspec +63 -0
- data/tasks/rdoc.rb +21 -0
- data/tasks/test.rb +17 -0
- data/tasks/vendor/sdoc-helpers/markdown.rb +56 -0
- data/tasks/vendor/sdoc-helpers/pages.rb +28 -0
- data/test/nice_easy_helpers_test.rb +621 -0
- data/test/tag_matcher.rb +52 -0
- data/test/test_helper.rb +15 -0
- metadata +91 -0
data/.document
ADDED
data/.gitignore
ADDED
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.
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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 = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
|
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
|