kiss 0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +19 -0
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/lib/kiss/action.rb +204 -0
- data/lib/kiss/controller_accessors.rb +101 -0
- data/lib/kiss/exception_report.rb +359 -0
- data/lib/kiss/form/field.rb +296 -0
- data/lib/kiss/form.rb +414 -0
- data/lib/kiss/format.rb +80 -0
- data/lib/kiss/hacks.rb +140 -0
- data/lib/kiss/iterator.rb +56 -0
- data/lib/kiss/mailer.rb +92 -0
- data/lib/kiss/model.rb +114 -0
- data/lib/kiss/rack/bench.rb +131 -0
- data/lib/kiss/rack/email_errors.rb +64 -0
- data/lib/kiss/rack/facebook.rb +28 -0
- data/lib/kiss/rack/file_not_found.rb +42 -0
- data/lib/kiss/rack/log_exceptions.rb +23 -0
- data/lib/kiss/rack/show_debug.rb +82 -0
- data/lib/kiss/rack/show_exceptions.rb +27 -0
- data/lib/kiss/sequel_mysql.rb +23 -0
- data/lib/kiss/sequel_session.rb +166 -0
- data/lib/kiss/template_methods.rb +125 -0
- data/lib/kiss.rb +725 -0
- metadata +108 -0
@@ -0,0 +1,359 @@
|
|
1
|
+
class Kiss
|
2
|
+
# Generates HTML exception reports for Rack::ShowExceptions and
|
3
|
+
# Rack::LogExceptions.
|
4
|
+
class ExceptionReport
|
5
|
+
@@context = 7
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def generate(env, exception)
|
9
|
+
req = Rack::Request.new(env)
|
10
|
+
path = (req.script_name + req.path_info).squeeze("/")
|
11
|
+
url = req.scheme + '://' + req.host + path
|
12
|
+
|
13
|
+
backtrace = exception.backtrace
|
14
|
+
#backtrace.shift while lines[0] =~ /\/lib\/kiss(\/|\.rb)/
|
15
|
+
|
16
|
+
frames = backtrace.map { |line|
|
17
|
+
frame = {}
|
18
|
+
if line =~ /(.*?):(\d+)(:in `(.*)')?/
|
19
|
+
frame.filename = $1
|
20
|
+
frame.lineno = $2.to_i
|
21
|
+
frame.function = $4
|
22
|
+
lines = nil
|
23
|
+
|
24
|
+
begin
|
25
|
+
lineno = frame.lineno-1
|
26
|
+
lines ||= ::File.readlines(frame.filename)
|
27
|
+
frame.pre_context_lineno = [lineno-@@context, 0].max
|
28
|
+
frame.pre_context = lines[frame.pre_context_lineno...lineno]
|
29
|
+
frame.context_line = lines[lineno].chomp
|
30
|
+
frame.post_context_lineno = [lineno+@@context, lines.size].min
|
31
|
+
frame.post_context = lines[lineno+1..frame.post_context_lineno]
|
32
|
+
rescue
|
33
|
+
end
|
34
|
+
|
35
|
+
frame
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
}.compact
|
40
|
+
|
41
|
+
env["rack.errors"].puts "#{exception.class}: #{exception.message}"
|
42
|
+
env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l }
|
43
|
+
env["rack.errors"].flush
|
44
|
+
|
45
|
+
db_query = begin (Sequel::MySQL::Database.last_query) rescue nil end
|
46
|
+
|
47
|
+
@@erubis ||= Erubis::Eruby.new(template)
|
48
|
+
@@erubis.result(binding)
|
49
|
+
end
|
50
|
+
|
51
|
+
def absolute_path(filename)
|
52
|
+
filename = ( filename =~ /\A\// ? '' : (Dir.pwd + '/') ) + filename
|
53
|
+
end
|
54
|
+
|
55
|
+
def textmate_href(frame)
|
56
|
+
"txmt://open?url=file://"+(h(absolute_path(frame.filename)))+"&line="+(h frame.lineno)
|
57
|
+
end
|
58
|
+
|
59
|
+
def h(obj) # :nodoc:
|
60
|
+
case obj
|
61
|
+
when String
|
62
|
+
Rack::Utils.escape_html(obj).gsub(/^(\s+)/) {' ' * $1.length}
|
63
|
+
else
|
64
|
+
Rack::Utils.escape_html(obj.inspect)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# :stopdoc:
|
69
|
+
|
70
|
+
# adapted from Django <djangoproject.com>
|
71
|
+
# Copyright (c) 2005, the Lawrence Journal-World
|
72
|
+
# Used under the modified BSD license:
|
73
|
+
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
74
|
+
def template
|
75
|
+
<<-EOT
|
76
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
77
|
+
<html lang="en">
|
78
|
+
<head>
|
79
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
80
|
+
<meta name="robots" content="NONE,NOARCHIVE" />
|
81
|
+
<title>Exception: <%=h exception.class %> - <%=h url %></title>
|
82
|
+
<style type="text/css">
|
83
|
+
html * { padding:0; margin:0; }
|
84
|
+
body *, .body * { padding:10px 20px; }
|
85
|
+
body * *, .body * * { padding:0; }
|
86
|
+
body { font:small sans-serif; }
|
87
|
+
body>div { border-bottom:1px solid #ddd; }
|
88
|
+
h1 { font-weight:normal; font-size: 18px; font-style: italic; color: #d96 }
|
89
|
+
h2 { margin-bottom:.8em; }
|
90
|
+
h2 span { font-size:80%; color:#000; font-weight:normal; }
|
91
|
+
h3 { margin:1em 0 .5em 0; }
|
92
|
+
h4 { margin:0 0 .5em 0; font-weight: normal; }
|
93
|
+
table {
|
94
|
+
border:1px solid #ccc; border-collapse: collapse; background:white; }
|
95
|
+
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
|
96
|
+
thead th {
|
97
|
+
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
|
98
|
+
font-weight:normal; font-size:11px; border:1px solid #ddd; }
|
99
|
+
tbody th { text-align:right; color:#666; padding-right:.5em; }
|
100
|
+
table.vars { margin:5px 0 2px 40px; }
|
101
|
+
table.vars td, table.req td { font-family:monospace; }
|
102
|
+
table td.code { width:100%;}
|
103
|
+
table td.code div { overflow:hidden; }
|
104
|
+
table.source th { color:#666; }
|
105
|
+
table.source td {
|
106
|
+
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
|
107
|
+
ul.traceback { list-style-type:none; }
|
108
|
+
ul.traceback li.frame { margin-bottom:1em; }
|
109
|
+
div.context { margin: 10px 0 10px 40px; background-color: #fff; }
|
110
|
+
div.context ol {
|
111
|
+
padding: 1px; margin-bottom: -1px; }
|
112
|
+
div.context ol li {
|
113
|
+
font-family:monospace; color:#666; cursor:pointer; padding: 0 2px; }
|
114
|
+
div.context ol.context-line li { color:black; background-color:#eca; }
|
115
|
+
div.context ol.context-line li span { float: right; }
|
116
|
+
div.commands { margin-left: 40px; }
|
117
|
+
div.commands a { color:black; text-decoration:none; }
|
118
|
+
#summary { background: #ffd; }
|
119
|
+
#summary h2 { font-weight: normal; color: #a10; font-size: 14px; font-weight: bold }
|
120
|
+
#summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
|
121
|
+
#summary ul#quicklinks li { float: left; padding: 0 1em; }
|
122
|
+
#summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
|
123
|
+
#explanation { background:#eee; }
|
124
|
+
#template, #template-not-exist { background:#f6f6f6; }
|
125
|
+
#template-not-exist ul { margin: 0 0 0 20px; }
|
126
|
+
#traceback { background:#eee; }
|
127
|
+
#requestinfo { background:#f6f6f6; padding-left:120px; }
|
128
|
+
#summary table { border:none; background:transparent; }
|
129
|
+
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
130
|
+
#requestinfo h3 { margin-bottom:-1em; }
|
131
|
+
.error { background: #ffc; }
|
132
|
+
.specific { color:#cc3300; font-weight:bold; }
|
133
|
+
</style>
|
134
|
+
<script type="text/javascript">
|
135
|
+
//<!--
|
136
|
+
function getElementsByClassName(oElm, strTagName, strClassName){
|
137
|
+
// Written by Jonathan Snook, http://www.snook.ca/jon;
|
138
|
+
// Add-ons by Robert Nyman, http://www.robertnyman.com
|
139
|
+
var arrElements = (strTagName == "*" && document.all)? document.all :
|
140
|
+
oElm.getElementsByTagName(strTagName);
|
141
|
+
var arrReturnElements = new Array();
|
142
|
+
strClassName = strClassName.replace(/\-/g, "\\-");
|
143
|
+
var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
|
144
|
+
var oElement;
|
145
|
+
for(var i=0; i<arrElements.length; i++){
|
146
|
+
oElement = arrElements[i];
|
147
|
+
if(oRegExp.test(oElement.className)){
|
148
|
+
arrReturnElements.push(oElement);
|
149
|
+
}
|
150
|
+
}
|
151
|
+
return (arrReturnElements)
|
152
|
+
}
|
153
|
+
function hideAll(elems) {
|
154
|
+
for (var e = 0; e < elems.length; e++) {
|
155
|
+
elems[e].style.display = 'none';
|
156
|
+
}
|
157
|
+
}
|
158
|
+
window.onload = function() {
|
159
|
+
hideAll(getElementsByClassName(document, 'table', 'vars'));
|
160
|
+
hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
|
161
|
+
hideAll(getElementsByClassName(document, 'ol', 'post-context'));
|
162
|
+
toggle('pre<%=h frames.first.object_id %>', 'post<%=h frames.first.object_id %>')
|
163
|
+
}
|
164
|
+
function toggle() {
|
165
|
+
for (var i = 0; i < arguments.length; i++) {
|
166
|
+
var e = document.getElementById(arguments[i]);
|
167
|
+
if (e) {
|
168
|
+
e.style.display = e.style.display == 'none' ? 'block' : 'none';
|
169
|
+
}
|
170
|
+
}
|
171
|
+
return false;
|
172
|
+
}
|
173
|
+
|
174
|
+
function varToggle(link, id) {
|
175
|
+
toggle('v' + id);
|
176
|
+
var s = link.getElementsByTagName('span')[0];
|
177
|
+
var uarr = String.fromCharCode(0x25b6);
|
178
|
+
var darr = String.fromCharCode(0x25bc);
|
179
|
+
s.innerHTML = s.innerHTML == uarr ? darr : uarr;
|
180
|
+
return false;
|
181
|
+
}
|
182
|
+
//-->
|
183
|
+
</script>
|
184
|
+
</head>
|
185
|
+
<body>
|
186
|
+
|
187
|
+
<div id="summary">
|
188
|
+
<a name="summary"></a>
|
189
|
+
<h1><%=h exception.class %></h1>
|
190
|
+
<h2><code><%=(h exception.message).gsub(/\n/,'<br>') %></code></h2>
|
191
|
+
<table><tr>
|
192
|
+
<th>Ruby</th>
|
193
|
+
<td><code><%=h frames.first.filename %></code>: in <code><%=h frames.first.function %></code>, <a href="<%= textmate_href(frames.first) %>">line <%=h frames.first.lineno %></a></td>
|
194
|
+
</tr><tr>
|
195
|
+
<th>Web</th>
|
196
|
+
<td><code><%=h req.request_method %> <%=h(url)%></code></td>
|
197
|
+
</tr></table>
|
198
|
+
|
199
|
+
<h3>Jump to:</h3>
|
200
|
+
<ul id="quicklinks">
|
201
|
+
<li><a href="#cache">Cache</a></li>
|
202
|
+
<li><a href="#db-query">DB Query</a></li>
|
203
|
+
<li><a href="#get-info">GET</a></li>
|
204
|
+
<li><a href="#post-info">POST</a></li>
|
205
|
+
<li><a href="#cookie-info">Cookies</a></li>
|
206
|
+
<li><a href="#env-info">ENV</a></li>
|
207
|
+
</ul>
|
208
|
+
</div>
|
209
|
+
|
210
|
+
<div id="traceback">
|
211
|
+
<h2>Traceback <span>(innermost first)</span></h2>
|
212
|
+
<ul class="traceback">
|
213
|
+
<% frames.each { |frame| %>
|
214
|
+
<li class="frame">
|
215
|
+
<a href="<%= textmate_href(frame) %>">line <%= h frame.lineno %></a> of <code><%=h frame.filename %></code> (in <code><b><%=h frame.function %></b></code>)
|
216
|
+
|
217
|
+
<% if frame.context_line %>
|
218
|
+
<div class="context" id="c<%=h frame.object_id %>">
|
219
|
+
<% if frame.pre_context %>
|
220
|
+
<ol start="<%=h frame.pre_context_lineno+1 %>" class="pre-context" id="pre<%=h frame.object_id %>">
|
221
|
+
<% frame.pre_context.each { |line| %>
|
222
|
+
<li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
|
223
|
+
<% } %>
|
224
|
+
</ol>
|
225
|
+
<% end %>
|
226
|
+
|
227
|
+
<ol start="<%=h frame.lineno %>" class="context-line">
|
228
|
+
<li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h frame.context_line %></li></ol>
|
229
|
+
|
230
|
+
<% if frame.post_context %>
|
231
|
+
<ol start='<%=h frame.lineno+1 %>' class="post-context" id="post<%=h frame.object_id %>">
|
232
|
+
<% frame.post_context.each { |line| %>
|
233
|
+
<li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
|
234
|
+
<% } %>
|
235
|
+
</ol>
|
236
|
+
<% end %>
|
237
|
+
</div>
|
238
|
+
<% end %>
|
239
|
+
</li>
|
240
|
+
<% } %>
|
241
|
+
</ul>
|
242
|
+
</div>
|
243
|
+
|
244
|
+
<div id="requestinfo">
|
245
|
+
<h2>Additional information</h2>
|
246
|
+
|
247
|
+
<a name="cache"></a>
|
248
|
+
<h3 id="cache">Cache</h3>
|
249
|
+
<p><code><%=h Kiss.exception_cache.inspect %></code></p>
|
250
|
+
|
251
|
+
<a name="db-query"></a>
|
252
|
+
<h3 id="db-query">DB Query</h3>
|
253
|
+
<p><% if db_query %><code><%=(h db_query).gsub(/\n/,'<br/>') %></code><% else %>No database queries executed.<% end %></p>
|
254
|
+
|
255
|
+
<a name="get-info"></a>
|
256
|
+
<h3 id="get-info">GET</h3>
|
257
|
+
<% unless req.GET.empty? %>
|
258
|
+
<table class="req">
|
259
|
+
<thead>
|
260
|
+
<tr>
|
261
|
+
<th>Variable</th>
|
262
|
+
<th>Value</th>
|
263
|
+
</tr>
|
264
|
+
</thead>
|
265
|
+
<tbody>
|
266
|
+
<% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
267
|
+
<tr>
|
268
|
+
<td><%=h key %></td>
|
269
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
270
|
+
</tr>
|
271
|
+
<% } %>
|
272
|
+
</tbody>
|
273
|
+
</table>
|
274
|
+
<% else %>
|
275
|
+
<p>No GET data.</p>
|
276
|
+
<% end %>
|
277
|
+
|
278
|
+
<a name="post-info"></a>
|
279
|
+
<h3 id="post-info">POST</h3>
|
280
|
+
<% unless req.POST.empty? %>
|
281
|
+
<table class="req">
|
282
|
+
<thead>
|
283
|
+
<tr>
|
284
|
+
<th>Variable</th>
|
285
|
+
<th>Value</th>
|
286
|
+
</tr>
|
287
|
+
</thead>
|
288
|
+
<tbody>
|
289
|
+
<% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
290
|
+
<tr>
|
291
|
+
<td><%=h key %></td>
|
292
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
293
|
+
</tr>
|
294
|
+
<% } %>
|
295
|
+
</tbody>
|
296
|
+
</table>
|
297
|
+
<% else %>
|
298
|
+
<p>No POST data.</p>
|
299
|
+
<% end %>
|
300
|
+
|
301
|
+
<a name="cookie-info"></a>
|
302
|
+
<h3 id="cookie-info">Cookies</h3>
|
303
|
+
<% unless req.cookies.empty? %>
|
304
|
+
<table class="req">
|
305
|
+
<thead>
|
306
|
+
<tr>
|
307
|
+
<th>Variable</th>
|
308
|
+
<th>Value</th>
|
309
|
+
</tr>
|
310
|
+
</thead>
|
311
|
+
<tbody>
|
312
|
+
<% req.cookies.each { |key, val| %>
|
313
|
+
<tr>
|
314
|
+
<td><%=h key %></td>
|
315
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
316
|
+
</tr>
|
317
|
+
<% } %>
|
318
|
+
</tbody>
|
319
|
+
</table>
|
320
|
+
<% else %>
|
321
|
+
<p>No cookie data.</p>
|
322
|
+
<% end %>
|
323
|
+
|
324
|
+
<a name="env-info"></a>
|
325
|
+
<h3 id="env-info">Rack ENV</h3>
|
326
|
+
<table class="req">
|
327
|
+
<thead>
|
328
|
+
<tr>
|
329
|
+
<th>Variable</th>
|
330
|
+
<th>Value</th>
|
331
|
+
</tr>
|
332
|
+
</thead>
|
333
|
+
<tbody>
|
334
|
+
<% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
335
|
+
<tr>
|
336
|
+
<td><%=h key %></td>
|
337
|
+
<td class="code"><div><%=h val %></div></td>
|
338
|
+
</tr>
|
339
|
+
<% } %>
|
340
|
+
</tbody>
|
341
|
+
</table>
|
342
|
+
|
343
|
+
</div>
|
344
|
+
|
345
|
+
<div id="explanation">
|
346
|
+
<p>
|
347
|
+
Generated by Kiss::ExceptionReport.
|
348
|
+
</p>
|
349
|
+
</div>
|
350
|
+
|
351
|
+
</body>
|
352
|
+
</html>
|
353
|
+
EOT
|
354
|
+
end
|
355
|
+
|
356
|
+
# :startdoc:
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
class Kiss
|
2
|
+
class Form
|
3
|
+
class Field
|
4
|
+
attr_accessor :name,:type,:form,:format,:currency,:label,:no_label,:prompt,:value,:ignore,:save,
|
5
|
+
:options,:options_value_key,:options_display_key,:required,:cancel,:columns,:style,:hidden_join
|
6
|
+
|
7
|
+
include Kiss::Form::AttributesSetter
|
8
|
+
|
9
|
+
def initialize(attrs)
|
10
|
+
@save = true
|
11
|
+
@value = ''
|
12
|
+
|
13
|
+
set_attributes(attrs,[:name,:type])
|
14
|
+
|
15
|
+
@errors = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def column_layout(elements_html)
|
19
|
+
if @columns
|
20
|
+
# format elements into cells
|
21
|
+
elements_html.map! { |el| "<td>" + el + "</td>" }
|
22
|
+
|
23
|
+
# compute height = ceiling(number of elements / columns)
|
24
|
+
height = ((elements_html.size + columns - 1)/columns).to_i
|
25
|
+
|
26
|
+
# group cells into columns
|
27
|
+
element_ranges = (1..height).to_a.map {|i| ((i-1)*@columns..(i*@columns-1))}
|
28
|
+
return ['<table class="kiss_field_columns">',element_ranges.map do |range|
|
29
|
+
["<tr>", elements_html[range], "</tr>"]
|
30
|
+
end,'</table>'].flatten.join
|
31
|
+
join_with = ''
|
32
|
+
else
|
33
|
+
join_with = ' '
|
34
|
+
end
|
35
|
+
|
36
|
+
# else columns <= 1
|
37
|
+
elements_html.join(join_with)
|
38
|
+
end
|
39
|
+
|
40
|
+
def sequel_value
|
41
|
+
case @format
|
42
|
+
when :date:
|
43
|
+
return Kiss.mdy_to_ymd(value)
|
44
|
+
when :datetime:
|
45
|
+
datetime = value.sub(/([ap]m)\s*\Z/i,' \1')
|
46
|
+
date, time, ampm = datetime.split(/\s+/)
|
47
|
+
|
48
|
+
hours, minutes = time.split(/:/)
|
49
|
+
hours = 0 if hours.to_i == 12 && ampm
|
50
|
+
if (ampm.downcase == 'pm')
|
51
|
+
hours = hours.to_i + 12
|
52
|
+
end
|
53
|
+
|
54
|
+
return Kiss.mdy_to_ymd(date) + " #{hours}:#{minutes}"
|
55
|
+
else
|
56
|
+
return value.is_a?(Array) ? value.map {|v| v.gsub(/,/,'\,')}.join(',') : value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def value
|
61
|
+
@value ||= @form.params[@name]
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_error(message)
|
65
|
+
@errors << message
|
66
|
+
@form.has_field_errors = true
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate
|
70
|
+
if (error = Kiss.validate_value(value,@format,@required))
|
71
|
+
add_error("#{@label} #{error}")
|
72
|
+
end
|
73
|
+
|
74
|
+
value
|
75
|
+
end
|
76
|
+
|
77
|
+
def errors_html
|
78
|
+
return nil unless @errors.size > 0
|
79
|
+
|
80
|
+
if @errors.size == 1
|
81
|
+
content = @errors[0]
|
82
|
+
else
|
83
|
+
content = "<ul>" + @errors.map {|e| "<li>#{e}</li>"}.join + "</ul>"
|
84
|
+
end
|
85
|
+
|
86
|
+
%Q(<span class="#{@form.field_error_class}">#{content}</span><br clear="all" />)
|
87
|
+
end
|
88
|
+
|
89
|
+
def element_html
|
90
|
+
if @currency == :dollars
|
91
|
+
return '$' + input_tag_html( :value => @value, :style => 'width: 80px' )
|
92
|
+
end
|
93
|
+
input_tag_html( :value => @value )
|
94
|
+
end
|
95
|
+
|
96
|
+
def html
|
97
|
+
errors = errors_html
|
98
|
+
element_html + (errors ? %Q(<br/>#{errors}) : '')
|
99
|
+
end
|
100
|
+
|
101
|
+
def input_tag_html(attrs = {}, extra_html = '')
|
102
|
+
attrs = attrs.clone
|
103
|
+
attrs[:name] ||= @name
|
104
|
+
attrs[:type] ||= @type
|
105
|
+
attrs[:style] ||= @style
|
106
|
+
|
107
|
+
attrs_html = []
|
108
|
+
attrs.each_pair {|k,v| attrs_html.push %Q(#{k}="#{v}") }
|
109
|
+
[ '<input', attrs_html, extra_html + '/>' ].flatten.join(' ')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class HiddenField < Field
|
114
|
+
end
|
115
|
+
class TextField < Field
|
116
|
+
end
|
117
|
+
|
118
|
+
class TextAreaField < Field
|
119
|
+
attr_accessor :rows,:cols
|
120
|
+
|
121
|
+
def initialize(*args)
|
122
|
+
@rows = 5
|
123
|
+
@cols = 20
|
124
|
+
super(*args)
|
125
|
+
end
|
126
|
+
def element_html
|
127
|
+
%Q(<textarea name="#{@name}" rows="#{@rows ||= 1}" cols="#{@cols ||= 1}" style="#{@style ||= ''}">#{@value}</textarea>)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class PasswordField < Field
|
132
|
+
def element_html
|
133
|
+
input_tag_html
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class BooleanField < Field
|
138
|
+
def element_html
|
139
|
+
input_tag_html({ :value => 1 }, @value ? 'CHECKED ' : '')
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class FileField < Field
|
144
|
+
def element_html
|
145
|
+
input_tag_html
|
146
|
+
end
|
147
|
+
|
148
|
+
def get_file_name
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
def get_file_data
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class SubmitField < Field
|
158
|
+
def initialize(*args)
|
159
|
+
@save = false
|
160
|
+
super(*args)
|
161
|
+
end
|
162
|
+
|
163
|
+
def element_html
|
164
|
+
elements_html.join(' ')
|
165
|
+
end
|
166
|
+
|
167
|
+
def validate
|
168
|
+
# do nothing for now
|
169
|
+
value
|
170
|
+
end
|
171
|
+
|
172
|
+
def elements_html
|
173
|
+
@options.map do |option|
|
174
|
+
input_tag_html({ :value => option })
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# ------ MultiChoiceField
|
180
|
+
|
181
|
+
class MultiChoiceField < Field
|
182
|
+
def option_pairs
|
183
|
+
if (defined? @options_value_key)
|
184
|
+
# options_display_map not supported
|
185
|
+
# default to options_display_key
|
186
|
+
return @options.map {|option| [ option[@options_value_key], option[@options_display_key] ]}
|
187
|
+
end
|
188
|
+
|
189
|
+
@options.map {|option| [ option, option ] }
|
190
|
+
end
|
191
|
+
|
192
|
+
def matched_options(value)
|
193
|
+
value = [value].flatten
|
194
|
+
@options_value_key ?
|
195
|
+
@options.select {|o| value.select {|v| o[@options_value_key] == v } }.flatten :
|
196
|
+
@options.select {|o| value.select {|v| o == v } }.flatten
|
197
|
+
end
|
198
|
+
|
199
|
+
def validate
|
200
|
+
@value = @form.params[@name]
|
201
|
+
|
202
|
+
if (@value =~ /\S/)
|
203
|
+
add_error "#{@label} invalid" unless matched_options(@value).size > 0
|
204
|
+
elsif @required
|
205
|
+
add_error "#{@label} required"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class SelectField < MultiChoiceField
|
211
|
+
def element_html
|
212
|
+
return 'No options' unless @options.size > 0
|
213
|
+
|
214
|
+
placeholder_html = %Q(<option value="">Choose Here</option>)
|
215
|
+
|
216
|
+
options_html = option_pairs.map do |value,display|
|
217
|
+
selected = (@value.to_s == value.to_s) ? ' selected' : ''
|
218
|
+
%Q(<option value="#{value}"#{selected}>#{display}</option>)
|
219
|
+
end
|
220
|
+
|
221
|
+
[%Q(<select name="#{@name}" style="#{@style}">), placeholder_html, options_html, '</select>'].flatten.join
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class RadioField < MultiChoiceField
|
226
|
+
def element_html
|
227
|
+
column_layout(elements_html)
|
228
|
+
end
|
229
|
+
|
230
|
+
def elements_html
|
231
|
+
return option_pairs.map do |value,display|
|
232
|
+
input_tag_html({ :value => value }, (@value.to_s == value.to_s) ? ' checked' : '' ) + display
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
# ------ MultiValueField
|
239
|
+
|
240
|
+
class MultiValueField < MultiChoiceField
|
241
|
+
def validate
|
242
|
+
@value = @form.params[@name.to_s+'[]'] || []
|
243
|
+
|
244
|
+
if @value.size > 0
|
245
|
+
@value.each do |value|
|
246
|
+
unless (matched_options(value).size > 0)
|
247
|
+
add_error "Invalid selection for #{@label}"
|
248
|
+
return
|
249
|
+
end
|
250
|
+
end
|
251
|
+
elsif @required
|
252
|
+
add_error "#{@label} required"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def selected_option_values
|
257
|
+
@selected_option_values ||= @value ? Hash[ *(@value.map {|v| [v.to_s,true]}.flatten) ] : {}
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class CheckboxField < MultiValueField
|
262
|
+
def element_html
|
263
|
+
if @hidden_join
|
264
|
+
hidden_options = input_tag_html(
|
265
|
+
:type => 'hidden',
|
266
|
+
:name => "#{@name}_options",
|
267
|
+
:value => option_pairs.map {|v,d| v}.join(@hidden_join)
|
268
|
+
)
|
269
|
+
else
|
270
|
+
hidden_options = ''
|
271
|
+
end
|
272
|
+
column_layout(elements_html) + hidden_options
|
273
|
+
end
|
274
|
+
|
275
|
+
def elements_html
|
276
|
+
return option_pairs.map do |value,display|
|
277
|
+
input_tag_html({ :name => @name.to_s+'[]', :value => value }, selected_option_values[value.to_s] ? ' checked' : '' ) + display.to_s
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
class MultiSelectField < MultiValueField
|
283
|
+
def element_html
|
284
|
+
options_html = option_pairs.map do |value,display|
|
285
|
+
selected = selected_option_values[value] ? ' selected' : ''
|
286
|
+
%Q(<option value="#{value}"#{selected}>#{display}</option>)
|
287
|
+
end
|
288
|
+
|
289
|
+
[%Q(<select multiple name="#{@name}[]" size="#{@size}" style="#{@style}">), options_html, '</select>'].flatten.join
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# not implemented yet:
|
296
|
+
# :multitext => MultiTextField,
|