nitro 0.31.0 → 0.40.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/nitro +135 -37
- data/doc/CHANGELOG.1 +108 -108
- data/doc/CHANGELOG.2 +89 -89
- data/doc/CHANGELOG.3 +105 -105
- data/{CHANGELOG → doc/CHANGELOG.4} +509 -509
- data/doc/{AUTHORS → CONTRIBUTORS} +49 -37
- data/doc/LIBRARIES +13 -0
- data/doc/LICENSE +2 -3
- data/doc/MIGRATION +45 -0
- data/doc/RELEASES +131 -11
- data/doc/TODO +67 -0
- data/lib/glue/magick.rb +0 -3
- data/lib/glue/sweeper.rb +30 -15
- data/lib/glue/thumbnails.rb +0 -2
- data/lib/glue/webfile.rb +23 -11
- data/lib/nitro.rb +37 -44
- data/lib/nitro/adapter/cgi.rb +0 -3
- data/lib/nitro/adapter/console.rb +0 -2
- data/lib/nitro/adapter/fastcgi.rb +6 -3
- data/lib/nitro/adapter/mongrel.rb +97 -58
- data/lib/nitro/adapter/script.rb +4 -6
- data/lib/nitro/adapter/webrick.rb +33 -87
- data/lib/nitro/adapter/webrick/vcr.rb +85 -0
- data/lib/nitro/caching.rb +0 -2
- data/lib/nitro/caching/actions.rb +0 -2
- data/lib/nitro/caching/fragments.rb +0 -2
- data/lib/nitro/caching/output.rb +45 -16
- data/lib/nitro/caching/proxy.rb +49 -0
- data/lib/nitro/cgi.rb +3 -6
- data/lib/nitro/cgi/cookie.rb +0 -3
- data/lib/nitro/cgi/request.rb +67 -24
- data/lib/nitro/cgi/response.rb +0 -2
- data/lib/nitro/cgi/{sendfile.rb → send_file.rb} +7 -6
- data/lib/nitro/compiler.rb +62 -55
- data/lib/nitro/compiler/cleanup.rb +0 -3
- data/lib/nitro/compiler/elements.rb +31 -28
- data/lib/nitro/compiler/errors.rb +2 -5
- data/lib/nitro/compiler/include.rb +10 -8
- data/lib/nitro/compiler/layout.rb +0 -2
- data/lib/nitro/compiler/localization.rb +0 -2
- data/lib/nitro/compiler/markup.rb +14 -6
- data/lib/nitro/compiler/morphing.rb +1 -5
- data/lib/nitro/compiler/script.rb +2 -4
- data/lib/nitro/compiler/squeeze.rb +0 -2
- data/lib/nitro/compiler/xslt.rb +0 -2
- data/lib/nitro/context.rb +10 -5
- data/lib/nitro/control.rb +18 -0
- data/lib/nitro/control/attribute.rb +88 -0
- data/lib/nitro/control/attribute/checkbox.rb +19 -0
- data/lib/nitro/control/attribute/datetime.rb +21 -0
- data/lib/nitro/control/attribute/file.rb +20 -0
- data/lib/nitro/control/attribute/fixnum.rb +26 -0
- data/lib/nitro/control/attribute/float.rb +26 -0
- data/lib/nitro/control/attribute/options.rb +38 -0
- data/lib/nitro/control/attribute/password.rb +16 -0
- data/lib/nitro/control/attribute/text.rb +16 -0
- data/lib/nitro/control/attribute/textarea.rb +16 -0
- data/lib/nitro/control/none.rb +16 -0
- data/lib/nitro/control/relation.rb +53 -0
- data/lib/nitro/control/relation/belongs_to.rb +0 -0
- data/lib/nitro/control/relation/has_many.rb +97 -0
- data/lib/nitro/control/relation/joins_many.rb +0 -0
- data/lib/nitro/control/relation/many_to_many.rb +0 -0
- data/lib/nitro/control/relation/refers_to.rb +29 -0
- data/lib/nitro/controller.rb +7 -296
- data/lib/nitro/dispatcher.rb +72 -34
- data/lib/nitro/element.rb +36 -10
- data/lib/nitro/element/javascript.rb +0 -2
- data/lib/nitro/flash.rb +23 -10
- data/lib/nitro/global.rb +36 -11
- data/lib/nitro/helper.rb +22 -8
- data/lib/nitro/helper/benchmark.rb +0 -2
- data/lib/nitro/helper/buffer.rb +0 -3
- data/lib/nitro/helper/css.rb +12 -0
- data/lib/nitro/helper/debug.rb +1 -3
- data/lib/nitro/helper/default.rb +1 -0
- data/lib/nitro/helper/feed.rb +400 -386
- data/lib/nitro/helper/form.rb +246 -116
- data/lib/nitro/helper/javascript.rb +28 -2
- data/lib/nitro/helper/javascript/morphing.rb +0 -2
- data/lib/nitro/helper/javascript/prototype.rb +0 -2
- data/lib/nitro/helper/javascript/scriptaculous.rb +0 -1
- data/lib/nitro/helper/layout.rb +0 -2
- data/lib/nitro/helper/navigation.rb +87 -0
- data/lib/nitro/helper/pager.rb +11 -22
- data/lib/nitro/helper/table.rb +9 -32
- data/lib/nitro/helper/url.rb +104 -0
- data/lib/nitro/helper/xhtml.rb +20 -4
- data/lib/nitro/helper/xml.rb +0 -2
- data/lib/nitro/markup.rb +131 -0
- data/lib/nitro/part.rb +52 -7
- data/lib/nitro/publishable.rb +328 -0
- data/lib/nitro/render.rb +30 -61
- data/lib/nitro/router.rb +12 -4
- data/lib/nitro/sanitize.rb +48 -0
- data/lib/nitro/scaffold.rb +9 -11
- data/lib/nitro/scaffold/controller.rb +25 -0
- data/lib/nitro/scaffold/model.rb +150 -0
- data/lib/nitro/scaffolding.rb +1 -3
- data/lib/nitro/server.rb +57 -32
- data/lib/nitro/server/drb.rb +16 -2
- data/lib/nitro/server/runner.rb +80 -102
- data/lib/nitro/service.rb +0 -1
- data/lib/nitro/service/xmlrpc.rb +0 -2
- data/lib/nitro/session.rb +26 -18
- data/lib/nitro/session/drb.rb +2 -16
- data/lib/nitro/session/memory.rb +0 -2
- data/lib/nitro/template.rb +219 -0
- data/lib/nitro/test/assertions.rb +1 -3
- data/lib/nitro/test/context.rb +0 -1
- data/lib/nitro/test/testcase.rb +0 -1
- data/lib/nitro/version.rb +6 -0
- data/lib/part/admin.rb +16 -0
- data/lib/part/admin/controller.rb +19 -0
- data/lib/part/admin/helper.rb +30 -0
- data/lib/part/admin/og/controller.rb +114 -0
- data/lib/part/admin/og/customize.rb +4 -0
- data/lib/part/admin/og/template/index.xhtml +27 -0
- data/lib/part/admin/og/template/list.xhtml +38 -0
- data/lib/part/admin/og/template/search.xhtml +20 -0
- data/lib/part/admin/og/template/update.xhtml +25 -0
- data/lib/part/admin/skin.rb +207 -0
- data/lib/part/admin/template/denied.xhtml +13 -0
- data/lib/part/admin/template/index.xhtml +12 -0
- data/lib/part/admin/todo.txt +2 -0
- data/proto/public/error.xhtml +4 -2
- data/proto/run.rb +0 -2
- data/test/glue/tc_webfile.rb +1 -0
- data/test/nitro/cgi/tc_request.rb +23 -0
- data/test/nitro/helper/tc_feed.rb +0 -3
- data/test/nitro/helper/tc_navbar.rb +74 -0
- data/test/nitro/helper/tc_table.rb +2 -0
- data/test/nitro/tc_cgi.rb +72 -19
- data/test/nitro/tc_controller.rb +35 -26
- data/test/nitro/tc_controller_aspect.rb +1 -0
- data/test/nitro/tc_controller_params.rb +864 -0
- data/test/nitro/tc_dispatcher.rb +2 -2
- data/test/nitro/tc_element.rb +16 -16
- data/test/nitro/tc_flash.rb +3 -3
- data/test/nitro/tc_markup.rb +31 -0
- data/test/nitro/tc_render.rb +12 -14
- data/test/nitro/tc_session.rb +9 -7
- data/test/nitro/tc_template.rb +34 -0
- metadata +217 -198
- data/INSTALL +0 -121
- data/ProjectInfo +0 -74
- data/README +0 -555
- data/doc/apache.txt +0 -9
- data/doc/config.txt +0 -28
- data/doc/faq.txt +0 -7
- data/doc/lhttpd.txt +0 -7
- data/lib/nitro/adapter/scgi.rb +0 -239
- data/lib/nitro/helper/form/builder.rb +0 -144
- data/lib/nitro/helper/form/controls.rb +0 -389
- data/lib/nitro/helper/rss.rb +0 -72
- data/proto/conf/apache.conf +0 -51
- data/proto/public/scaffold/advanced_search.xhtml +0 -30
- data/proto/public/scaffold/edit.xhtml +0 -11
- data/proto/public/scaffold/form.xhtml +0 -1
- data/proto/public/scaffold/index.xhtml +0 -20
- data/proto/public/scaffold/list.xhtml +0 -32
- data/proto/public/scaffold/new.xhtml +0 -11
- data/proto/public/scaffold/search.xhtml +0 -29
- data/proto/public/scaffold/view.xhtml +0 -8
- data/proto/script/scgi_ctl +0 -221
- data/proto/script/scgi_service +0 -128
- data/setup.rb +0 -1585
- data/src/part/admin.rb +0 -16
- data/src/part/admin/controller.rb +0 -81
- data/src/part/admin/skin.rb +0 -21
- data/src/part/admin/system.css +0 -135
- data/src/part/admin/template/denied.xhtml +0 -1
- data/src/part/admin/template/index.xhtml +0 -43
- data/test/nitro/helper/tc_rss.rb +0 -24
data/doc/apache.txt
DELETED
data/doc/config.txt
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
= Configuration parameters
|
2
|
-
|
3
|
-
This file presents a complete list of Nitro Configuration
|
4
|
-
Parameters.
|
5
|
-
|
6
|
-
INFO: To browse a documented listing of an application's settings
|
7
|
-
point your browser to:
|
8
|
-
|
9
|
-
http://www.myapp.com/settings.
|
10
|
-
|
11
|
-
|
12
|
-
=== Session.store_type = :memory
|
13
|
-
|
14
|
-
Selects the mechanism employed for storing the sessions.
|
15
|
-
Available values are:
|
16
|
-
|
17
|
-
[+:memory+]
|
18
|
-
This is the default mechanism, sessions are stored
|
19
|
-
in memory. Only useful in multithreaded environments
|
20
|
-
like WEBrick.
|
21
|
-
|
22
|
-
[+:drb+]
|
23
|
-
Distributed Sessions using DRb. An independed DRb
|
24
|
-
server stores the sessions.
|
25
|
-
|
26
|
-
In the future there will be more options available (:memcache,
|
27
|
-
:filesys, :db)
|
28
|
-
|
data/doc/faq.txt
DELETED
data/doc/lhttpd.txt
DELETED
data/lib/nitro/adapter/scgi.rb
DELETED
@@ -1,239 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'stringio'
|
4
|
-
require 'yaml'
|
5
|
-
require 'digest/sha1'
|
6
|
-
require 'socket'
|
7
|
-
require 'cgi'
|
8
|
-
require 'monitor'
|
9
|
-
require 'singleton'
|
10
|
-
|
11
|
-
module SCGI # :nodoc: all
|
12
|
-
|
13
|
-
# Modifies CGI so that we can use it.
|
14
|
-
class SCGIFixed < ::CGI # :nodoc: all
|
15
|
-
public :env_table
|
16
|
-
|
17
|
-
def initialize(params, data, out, *args)
|
18
|
-
@env_table = params
|
19
|
-
@args = *args
|
20
|
-
@input = StringIO.new(data)
|
21
|
-
@out = out
|
22
|
-
super(*args)
|
23
|
-
end
|
24
|
-
def args
|
25
|
-
@args
|
26
|
-
end
|
27
|
-
def env_table
|
28
|
-
@env_table
|
29
|
-
end
|
30
|
-
alias_method :env, :env_table
|
31
|
-
|
32
|
-
def stdinput
|
33
|
-
@input
|
34
|
-
end
|
35
|
-
def stdoutput
|
36
|
-
@out
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class SCGIProcessor < Monitor # :nodoc: all
|
41
|
-
attr_reader :settings
|
42
|
-
|
43
|
-
def initialize(server, settings = {})
|
44
|
-
@conns = 0
|
45
|
-
@shutdown = false
|
46
|
-
@dead = false
|
47
|
-
@server = server
|
48
|
-
super()
|
49
|
-
|
50
|
-
configure(settings)
|
51
|
-
end
|
52
|
-
|
53
|
-
def configure(settings)
|
54
|
-
@settings = settings
|
55
|
-
#@log = LogFactory.instance.create(settings[:logfile])
|
56
|
-
@log = Logger
|
57
|
-
@log = Logger.new(settings[:logfile]) if settings[:logfile]
|
58
|
-
|
59
|
-
@maxconns = settings[:maxconns]
|
60
|
-
@started = Time.now
|
61
|
-
|
62
|
-
if settings[:socket]
|
63
|
-
@socket = settings[:socket]
|
64
|
-
else
|
65
|
-
@host = settings[:host]
|
66
|
-
@port = settings[:port]
|
67
|
-
end
|
68
|
-
|
69
|
-
@throttle_sleep = 1.0/settings[:conns_second] if settings[:conns_second]
|
70
|
-
end
|
71
|
-
|
72
|
-
def listen
|
73
|
-
@socket = TCPServer.new(@host, @port)
|
74
|
-
|
75
|
-
begin
|
76
|
-
while true
|
77
|
-
handle_client(@socket.accept)
|
78
|
-
sleep @throttle_sleep if @throttle_sleep
|
79
|
-
break if @shutdown and @conns <= 0
|
80
|
-
end
|
81
|
-
rescue Interrupt
|
82
|
-
@log.info("SCGI: Shutting down from SIGINT.")
|
83
|
-
rescue Object
|
84
|
-
@log.warn("SCGI: while listening for connections on #@host:#@port -- #{$!.class} #$! #{$!.backtrace.join("\n")}" )
|
85
|
-
end
|
86
|
-
|
87
|
-
@socket.close if not @socket.closed?
|
88
|
-
@dead = true
|
89
|
-
@log.info("SCGI: Exited accept loop. Shutdown complete.")
|
90
|
-
end
|
91
|
-
|
92
|
-
|
93
|
-
def handle_client(socket)
|
94
|
-
Thread.new do
|
95
|
-
begin
|
96
|
-
# remember if we were doing a shutdown so we can avoid increment later
|
97
|
-
in_shutdown = @shutdown
|
98
|
-
synchronize { @conns += 1 if not in_shutdown }
|
99
|
-
|
100
|
-
len = ""
|
101
|
-
# we only read 10 bytes of the length. any request longer than this is invalid
|
102
|
-
while len.length <= 10
|
103
|
-
c = socket.read(1)
|
104
|
-
if c == ':'
|
105
|
-
# found the terminal, len now has a length in it so read the payload
|
106
|
-
break
|
107
|
-
else
|
108
|
-
len << c
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# we should now either have a payload length to get
|
113
|
-
payload = socket.read(len.to_i)
|
114
|
-
if (c = socket.read(1)) != ','
|
115
|
-
@log.warn("SCGI: Malformed request, does not end with ','")
|
116
|
-
else
|
117
|
-
read_header(socket, payload, @conns)
|
118
|
-
end
|
119
|
-
rescue IOError
|
120
|
-
@log.warn("SCGI: received IOError #$! when handling client. Your web server doesn't like me.")
|
121
|
-
rescue Object
|
122
|
-
@log.warn("SCGI: after accepting client #@host:#@port -- #{$!.class} #$! #{$!.backtrace.join("\n")}")
|
123
|
-
ensure
|
124
|
-
synchronize { @conns -= 1 if not in_shutdown}
|
125
|
-
socket.close if not socket.closed?
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
|
131
|
-
def read_header(socket, payload, conns)
|
132
|
-
return if socket.closed?
|
133
|
-
request = split_body(payload)
|
134
|
-
if request["CONTENT_LENGTH"]
|
135
|
-
length = request["CONTENT_LENGTH"].to_i
|
136
|
-
if length > 0
|
137
|
-
body = socket.read(length)
|
138
|
-
else
|
139
|
-
body = ""
|
140
|
-
end
|
141
|
-
|
142
|
-
if @shutdown or @conns > @maxconns
|
143
|
-
socket.write("Location: /busy.html\r\n")
|
144
|
-
socket.write("Cache-control: no-cache, must-revalidate\r\n")
|
145
|
-
socket.write("Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n")
|
146
|
-
socket.write("Status: 307 Temporary Redirect\r\n\r\n")
|
147
|
-
else
|
148
|
-
process_request(request, body, socket)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
|
154
|
-
def process_request(request, body, socket)
|
155
|
-
return if socket.closed?
|
156
|
-
cgi = SCGIFixed.new(request, body, socket)
|
157
|
-
begin
|
158
|
-
#--
|
159
|
-
# TODO: remove sync, Nitro *is* thread safe!
|
160
|
-
#++
|
161
|
-
# guill: and why not ? ;)
|
162
|
-
#synchronize do
|
163
|
-
#--
|
164
|
-
# FIXME: this is uggly, something better?
|
165
|
-
#++
|
166
|
-
cgi.stdinput.rewind
|
167
|
-
cgi.env["QUERY_STRING"] = (cgi.env["REQUEST_URI"] =~ /^[^?]+\?(.+)$/ and $1).to_s
|
168
|
-
Nitro::Cgi.process(@server, cgi, cgi.stdinput, cgi.stdoutput)
|
169
|
-
#end
|
170
|
-
ensure
|
171
|
-
Og.manager.put_store if defined?(Og) and Og.respond_to?(:manager) and Og.manager
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def split_body(data)
|
176
|
-
result = {}
|
177
|
-
el = data.split("\0")
|
178
|
-
i = 0
|
179
|
-
len = el.length
|
180
|
-
while i < len
|
181
|
-
result[el[i]] = el[i+1]
|
182
|
-
i += 2
|
183
|
-
end
|
184
|
-
|
185
|
-
return result
|
186
|
-
end
|
187
|
-
|
188
|
-
|
189
|
-
def status
|
190
|
-
{
|
191
|
-
:time => Time.now, :pid => Process.pid, :settings => @settings,
|
192
|
-
:env => @settings[:env], :started => @started,
|
193
|
-
:max_conns => @maxconns, :conns => @conns, :systimes => Process.times,
|
194
|
-
:conns_second => @throttle, :shutdown => @shutdown, :dead => @dead
|
195
|
-
}
|
196
|
-
end
|
197
|
-
|
198
|
-
# Graceful shutdown is done by setting the maxconns to 0 so that all new requests
|
199
|
-
# get the 503 Service Unavailable status. The handler code checks if @shutdown is
|
200
|
-
# set and if so it will not increase the @conns count, but it will decrease.
|
201
|
-
# Once the @conns count is down to 0 it will exit the loop.
|
202
|
-
def shutdown(force = false)
|
203
|
-
@shutdown = true;
|
204
|
-
|
205
|
-
if force
|
206
|
-
@socket.close
|
207
|
-
@log.info("SCGI: FORCED shutdown requested. Oh well.")
|
208
|
-
else
|
209
|
-
@log.info("SCGI: Shutdown requested. Beginning graceful shutdown with #@conns connected.")
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
class << self
|
215
|
-
def start(server)
|
216
|
-
settings = {
|
217
|
-
:host => server.address,
|
218
|
-
:port => server.port,
|
219
|
-
:logfile => nil, # will use Logger
|
220
|
-
:maxconns => 2**30-1,
|
221
|
-
:socket => nil,
|
222
|
-
:conns_second => nil,
|
223
|
-
:env => nil,
|
224
|
-
:drb_enable => false,
|
225
|
-
:drb_port => server.port - 1000,
|
226
|
-
:drb_password => ""
|
227
|
-
}
|
228
|
-
|
229
|
-
settings.update(server.options)
|
230
|
-
|
231
|
-
@nitro = SCGIProcessor.new(server, settings)
|
232
|
-
Logger.info("SCGI: Running on #{settings[:host]}:#{settings[:port]}")
|
233
|
-
@nitro.listen
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
# * Zed A Shaw <zedshaw@zedshaw.com>
|
239
|
-
# * George Moschovitis <gm@navel.gr>
|
@@ -1,144 +0,0 @@
|
|
1
|
-
require 'glue/builder/xml'
|
2
|
-
|
3
|
-
module Nitro
|
4
|
-
module FormHelper
|
5
|
-
|
6
|
-
# A specialized Builder for dynamically building of forms.
|
7
|
-
# Provides extra support for forms backed by managed objects
|
8
|
-
# (entities).
|
9
|
-
#--
|
10
|
-
# TODO: allow multiple objects per form.
|
11
|
-
# TODO: use more generalized controls.
|
12
|
-
#++
|
13
|
-
|
14
|
-
class FormXmlBuilder < ::Glue::XmlBuilder
|
15
|
-
|
16
|
-
def initialize buffer = '', options = {}
|
17
|
-
super
|
18
|
-
@obj = options[:object]
|
19
|
-
end
|
20
|
-
|
21
|
-
# Render a control+label for the given property of the form
|
22
|
-
# object.
|
23
|
-
|
24
|
-
def property sym, options = {}
|
25
|
-
if prop = @obj.class.properties[sym]
|
26
|
-
control = Form::Control.fetch(@obj, prop, options).render
|
27
|
-
print element(prop, control)
|
28
|
-
else
|
29
|
-
raise "Undefined property '#{sym}' for class '#{@obj.class}'."
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# Render controls+labels for all relations of the form object.
|
34
|
-
|
35
|
-
def all_properties options = {}
|
36
|
-
for prop in @obj.class.properties.values
|
37
|
-
property prop.symbol, options
|
38
|
-
end
|
39
|
-
end
|
40
|
-
alias_method :properties, :all_properties
|
41
|
-
|
42
|
-
#--
|
43
|
-
# IMPLEMENT ME
|
44
|
-
#++
|
45
|
-
|
46
|
-
def relation sym, options = {}
|
47
|
-
end
|
48
|
-
|
49
|
-
#--
|
50
|
-
# IMPLEMENT ME
|
51
|
-
#++
|
52
|
-
|
53
|
-
def all_relations options = {}
|
54
|
-
end
|
55
|
-
|
56
|
-
# Renders a control to select a file for upload.
|
57
|
-
|
58
|
-
def select_file name, options = {}
|
59
|
-
print %|<input type="file" name="#{name}" />|
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
# Emit a label. Override this method to customize the
|
65
|
-
# rendering for your application needs.
|
66
|
-
|
67
|
-
def label prop
|
68
|
-
%{<label for="#{prop.name}">#{prop[:title] || prop.name.to_s.humanize}</label>}
|
69
|
-
end
|
70
|
-
|
71
|
-
# Emit a form element. Override this method to customize the
|
72
|
-
# rendering for your application needs.
|
73
|
-
|
74
|
-
def element prop, html
|
75
|
-
%{
|
76
|
-
<p class="form_#{prop.symbol}">
|
77
|
-
<div>#{label(prop)}</div>
|
78
|
-
#{html}
|
79
|
-
</p>
|
80
|
-
}
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
|
85
|
-
# A sophisticated form generation helper method.
|
86
|
-
#
|
87
|
-
# === Options
|
88
|
-
#
|
89
|
-
# * :object, :entity, :class = The object that acts as model
|
90
|
-
# for this form. If you pass a class an empty object is
|
91
|
-
# instantiated.
|
92
|
-
#
|
93
|
-
# * :action = The action of this form. The parameter is
|
94
|
-
# passed through the R operator (encode_url) to support
|
95
|
-
# advanced url encoding.
|
96
|
-
#
|
97
|
-
# === Example
|
98
|
-
#
|
99
|
-
# #{form(:object => @owner, :action => :save_profile) do |f|
|
100
|
-
# f.property :name, :editable => false
|
101
|
-
# f.property :password
|
102
|
-
# f.br
|
103
|
-
# f.submit 'Update'
|
104
|
-
# end}
|
105
|
-
|
106
|
-
def form options = {}, &block
|
107
|
-
obj = (options[:object] ||= options[:entity] || options[:class])
|
108
|
-
|
109
|
-
# If the passed obj is a Class instantiate an empty object
|
110
|
-
# of this class.
|
111
|
-
|
112
|
-
if obj.is_a? Class
|
113
|
-
obj = options[:object] = obj.allocate
|
114
|
-
end
|
115
|
-
|
116
|
-
# Convert virtual :multipart method to method="post",
|
117
|
-
# enctype="multipart/form-data"
|
118
|
-
|
119
|
-
if options[:method] == :multipart
|
120
|
-
options[:method] = :post
|
121
|
-
options[:enctype] = 'multipart/form-data'
|
122
|
-
end
|
123
|
-
|
124
|
-
b = FormXmlBuilder.new('', options)
|
125
|
-
|
126
|
-
b << '<form'
|
127
|
-
b << %| action="#{R options[:action]}"| if options[:action]
|
128
|
-
b << %| method="#{options[:method]}"| if options[:method]
|
129
|
-
b << %| enctype="#{options[:enctype]}"| if options[:enctype]
|
130
|
-
b << '>'
|
131
|
-
|
132
|
-
b.hidden(:name => 'oid', :value => obj.oid) if obj and obj.saved?
|
133
|
-
|
134
|
-
yield b
|
135
|
-
|
136
|
-
b << '</form>'
|
137
|
-
|
138
|
-
return b
|
139
|
-
end
|
140
|
-
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
# * George Moschovitis <gm@navel.gr>
|
@@ -1,389 +0,0 @@
|
|
1
|
-
require 'glue/configuration'
|
2
|
-
|
3
|
-
require 'nitro/helper/xhtml'
|
4
|
-
|
5
|
-
module Nitro
|
6
|
-
|
7
|
-
# :section: Property controls.
|
8
|
-
|
9
|
-
module Form
|
10
|
-
|
11
|
-
# A Form control.
|
12
|
-
|
13
|
-
class Control
|
14
|
-
include Nitro::XhtmlHelper
|
15
|
-
|
16
|
-
# Fetch the instance vars in a nice way use either rel
|
17
|
-
# or prop.
|
18
|
-
#
|
19
|
-
# values/value contain the contents of the prop or rel,
|
20
|
-
# (values reads better for relations)
|
21
|
-
|
22
|
-
attr_reader :prop
|
23
|
-
alias_method :rel, :prop
|
24
|
-
|
25
|
-
attr_reader :obj
|
26
|
-
|
27
|
-
attr_reader :value
|
28
|
-
alias_method :values, :value
|
29
|
-
|
30
|
-
# setup instance vars to use in methods
|
31
|
-
|
32
|
-
def initialize(obj, key, options = {})
|
33
|
-
@obj = obj
|
34
|
-
@prop = key
|
35
|
-
@value = options[:value] || obj.send(key.name.to_sym)
|
36
|
-
@options = options
|
37
|
-
end
|
38
|
-
|
39
|
-
# Main bulk of the control. Overide to customise
|
40
|
-
|
41
|
-
def render
|
42
|
-
"No view for this control"
|
43
|
-
end
|
44
|
-
|
45
|
-
# Label item. Override to customise
|
46
|
-
|
47
|
-
def label
|
48
|
-
%{<label for="#{prop.name}">#{prop[:title] || prop.name.to_s.humanize}</label>}
|
49
|
-
end
|
50
|
-
|
51
|
-
# Custom callback to process the request information
|
52
|
-
# posted back from the form
|
53
|
-
#
|
54
|
-
# When Property.populate_object (or fill) is called
|
55
|
-
# with the :preprocess => true option then this
|
56
|
-
# method will get called before the value is pushed
|
57
|
-
# onto the object that is getting 'filled'
|
58
|
-
#
|
59
|
-
# Overide this on controls that require special mods
|
60
|
-
# to the incoming values
|
61
|
-
|
62
|
-
def on_populate(val)
|
63
|
-
return val
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def emit_style
|
69
|
-
if prop.respond_to?(:control_style)
|
70
|
-
style = prop.control_style
|
71
|
-
elsif self.class.respond_to?(:style)
|
72
|
-
style = self.class.style
|
73
|
-
else
|
74
|
-
style = nil
|
75
|
-
end
|
76
|
-
style ? %{ style="#{style}"} : ''
|
77
|
-
end
|
78
|
-
|
79
|
-
# add support to your controls for being disabled
|
80
|
-
# by including an emit_disabled on form items
|
81
|
-
# or testing for is_disabled? on more complex controls
|
82
|
-
|
83
|
-
def emit_disabled
|
84
|
-
is_disabled? ? %{ disabled="disabled"} : ''
|
85
|
-
end
|
86
|
-
|
87
|
-
def is_disabled?
|
88
|
-
return false if @options[:all]
|
89
|
-
@options[:disable_controls] || @prop.disable_control
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# Fixnum
|
94
|
-
|
95
|
-
class FixnumControl < Control
|
96
|
-
setting :style, :default => 'width: 100px', :doc => 'The default style'
|
97
|
-
|
98
|
-
def render
|
99
|
-
style = prop.control_style ||self.class.style
|
100
|
-
%{
|
101
|
-
<div class="numeric_ctl_container">
|
102
|
-
<span class="numeric_ctl_input">
|
103
|
-
<input type="text" id="#{prop.symbol}_ctl" name="#{prop.symbol}" value="#{value}"#{emit_style}#{emit_disabled} />
|
104
|
-
</span>
|
105
|
-
<span class="numeric_ctl_buttons">
|
106
|
-
<a href="#" onclick="el=document.getElementById('#{prop.symbol}_ctl'); el.value=(parseInt(el.value) || 0)+#{step}; return false;">+</a>
|
107
|
-
<a href="#" onclick="el=document.getElementById('#{prop.symbol}_ctl'); el.value=(parseInt(el.value) || 0)-#{step}; return false;">-</a>
|
108
|
-
</span>
|
109
|
-
</div>
|
110
|
-
}
|
111
|
-
end
|
112
|
-
|
113
|
-
def step
|
114
|
-
1
|
115
|
-
end
|
116
|
-
|
117
|
-
end
|
118
|
-
|
119
|
-
# Float
|
120
|
-
|
121
|
-
class FloatControl < FixnumControl
|
122
|
-
setting :style, :default => 'width: 100px', :doc => 'The default style'
|
123
|
-
|
124
|
-
def step
|
125
|
-
0.1
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
# Text
|
130
|
-
|
131
|
-
class TextControl < Control
|
132
|
-
setting :style, :default => 'width: 250px', :doc => 'The default style'
|
133
|
-
|
134
|
-
def render
|
135
|
-
%{<input type="text" id="#{prop.symbol}_ctl" name="#{prop.symbol}" value="#{value}"#{emit_style}#{emit_disabled} />}
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
# Password
|
140
|
-
|
141
|
-
class PasswordControl < Control
|
142
|
-
setting :style, :default => 'width: 250px', :doc => 'The default style'
|
143
|
-
|
144
|
-
def render
|
145
|
-
%{<input type="password" id="#{prop.symbol}_ctl" name="#{prop.symbol}" value="#{value}"#{emit_style} />}
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
# Textarea
|
150
|
-
|
151
|
-
class TextareaControl < Control
|
152
|
-
setting :style, :default => 'width: 500px; height: 100px', :doc => 'The default style'
|
153
|
-
|
154
|
-
def render
|
155
|
-
%{<textarea id="#{prop.symbol}_ctl" name="#{prop.symbol}"#{emit_style}#{emit_disabled}>#{value}</textarea>}
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
# CheckboxControl < Control
|
160
|
-
|
161
|
-
class CheckboxControl < Control
|
162
|
-
setting :style, :default => '', :doc => 'The default style'
|
163
|
-
|
164
|
-
def render
|
165
|
-
checked = value == true ? ' checked="checked"':''
|
166
|
-
%{<input type="checkbox" id="#{prop.symbol}_ctl" name="#{prop.symbol}" value="true"#{emit_style}#{checked}#{emit_disabled} />}
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# ArrayControl
|
171
|
-
|
172
|
-
class ArrayControl < Control
|
173
|
-
def render
|
174
|
-
str = emit_container_start
|
175
|
-
str << emit_js
|
176
|
-
if values.empty?
|
177
|
-
str << emit_array_element(:removable => false)
|
178
|
-
else
|
179
|
-
removable = values.size != 1 ? true : false
|
180
|
-
values.each do |item|
|
181
|
-
str << emit_array_element()
|
182
|
-
end
|
183
|
-
end
|
184
|
-
str << emit_container_end
|
185
|
-
end
|
186
|
-
|
187
|
-
def emit_array_element(options={})
|
188
|
-
removable = options.fetch(:removable, true)
|
189
|
-
%{
|
190
|
-
<div>
|
191
|
-
<input type="text" id="#{prop.symbol}_ctl" name="#{prop.symbol}[]" value="#{value}"#{emit_style}#{emit_disabled} />
|
192
|
-
<input type="button" class="#{prop.symbol}_remove_btn" value=" - " onclick="rm_#{prop.symbol}_rel(this);" #{'disabled="disabled"' unless removable} />
|
193
|
-
<input type="button" class="#{prop.symbol}_add_btn" value=" + " onclick="add_#{prop.symbol}_rel(this);"#{emit_disabled} />
|
194
|
-
</div>
|
195
|
-
}
|
196
|
-
end
|
197
|
-
|
198
|
-
def emit_container_start
|
199
|
-
%{<div class="array_container">}
|
200
|
-
end
|
201
|
-
|
202
|
-
def emit_container_end
|
203
|
-
%{</div>}
|
204
|
-
end
|
205
|
-
|
206
|
-
def emit_js
|
207
|
-
%{
|
208
|
-
<script type="text/javascript">
|
209
|
-
rm_#{prop.symbol}_rel = function(el){
|
210
|
-
ctl=el.parentNode;
|
211
|
-
container=ctl.parentNode;
|
212
|
-
container.removeChild(ctl);
|
213
|
-
inputTags = container.getElementsByTagName('input');
|
214
|
-
if(inputTags.length==2)
|
215
|
-
inputTags[0].disabled='disabled';
|
216
|
-
}
|
217
|
-
add_#{prop.symbol}_rel = function(el){
|
218
|
-
ctl=el.parentNode;
|
219
|
-
container=ctl.parentNode;
|
220
|
-
node=ctl.cloneNode(true);
|
221
|
-
node.getElementsByTagName('input')[0].removeAttribute('disabled');
|
222
|
-
if(container.lastChild==ctl) container.appendChild(node);
|
223
|
-
else container.insertBefore(node, ctl.nextSibling);
|
224
|
-
if(container.childNodes.length>1) container.getElementsByTagName('input')[1].disabled='';
|
225
|
-
}
|
226
|
-
</script>
|
227
|
-
}
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
# :section: Relation controls.
|
232
|
-
|
233
|
-
# RefersTo. Also used for BelongsTo.
|
234
|
-
|
235
|
-
class RefersToControl < Control
|
236
|
-
def render
|
237
|
-
%{
|
238
|
-
<select id="#{rel.name}_ctl" name="#{rel.name}"#{emit_disabled}>
|
239
|
-
#{emit_options}
|
240
|
-
</select>
|
241
|
-
}
|
242
|
-
end
|
243
|
-
|
244
|
-
def emit_options
|
245
|
-
objs = rel.target_class.all
|
246
|
-
selected = selected.pk if selected = value
|
247
|
-
%{
|
248
|
-
<option value="nil">None</option>
|
249
|
-
#{options(:labels => objs.map{|o| o.to_s}, :values => objs.map{|o| o.pk}, :selected => selected)}
|
250
|
-
}
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
# HasMany, ManyToMany and JoinsMany
|
255
|
-
|
256
|
-
class HasManyControl < Control
|
257
|
-
|
258
|
-
#pre :do_this, :on => :populate_object
|
259
|
-
|
260
|
-
def render
|
261
|
-
str = emit_container_start
|
262
|
-
str << emit_js
|
263
|
-
if selected_items.empty?
|
264
|
-
str << emit_selector(:removable => false)
|
265
|
-
else
|
266
|
-
removable = selected_items.size != 1 ? true : false
|
267
|
-
selected_items.each do |item|
|
268
|
-
str << emit_selector(:selected => item.pk)
|
269
|
-
end
|
270
|
-
end
|
271
|
-
str << emit_container_end
|
272
|
-
end
|
273
|
-
|
274
|
-
private
|
275
|
-
|
276
|
-
# these parts are seperated from render to make it easier
|
277
|
-
# to extend and customise the HasManyControl
|
278
|
-
|
279
|
-
def all_items
|
280
|
-
return @all_items unless @all_items.nil?
|
281
|
-
@all_items = rel.target_class.all
|
282
|
-
end
|
283
|
-
|
284
|
-
def selected_items
|
285
|
-
values
|
286
|
-
end
|
287
|
-
|
288
|
-
def emit_container_start
|
289
|
-
%{<div class="many_to_many_container">}
|
290
|
-
end
|
291
|
-
|
292
|
-
def emit_container_end
|
293
|
-
%{</div>}
|
294
|
-
end
|
295
|
-
|
296
|
-
# :removable controls wether the minus button is active
|
297
|
-
# :selected denotes the oid to flag as selected in the list
|
298
|
-
|
299
|
-
def emit_selector(options={})
|
300
|
-
removable = options.fetch(:removable, true)
|
301
|
-
selected = options.fetch(:selected, nil)
|
302
|
-
%{
|
303
|
-
<div>
|
304
|
-
<select class="has_many_ctl" name="#{rel.name}[]" #{emit_style}#{emit_disabled}>
|
305
|
-
<option value="nil">None</option>
|
306
|
-
#{options(:labels => all_items.map{|o| o.to_s}, :values => all_items.map{|o| o.pk}, :selected => selected)}
|
307
|
-
</select>
|
308
|
-
<input type="button" class="#{rel.name}_remove_btn" value=" - " onclick="rm_#{rel.name}_rel(this);" #{'disabled="disabled"' unless removable} />
|
309
|
-
<input type="button" class="#{rel.name}_add_btn" value=" + " onclick="add_#{rel.name}_rel(this);"#{emit_disabled} />
|
310
|
-
</div>
|
311
|
-
}
|
312
|
-
end
|
313
|
-
|
314
|
-
# Inline script: override this to change behavior
|
315
|
-
|
316
|
-
def emit_js
|
317
|
-
%{
|
318
|
-
<script type="text/javascript">
|
319
|
-
rm_#{rel.name}_rel = function(el){
|
320
|
-
ctl=el.parentNode;
|
321
|
-
container=ctl.parentNode;
|
322
|
-
container.removeChild(ctl);
|
323
|
-
inputTags = container.getElementsByTagName('input');
|
324
|
-
if(inputTags.length==2)
|
325
|
-
inputTags[0].disabled='disabled';
|
326
|
-
}
|
327
|
-
add_#{rel.name}_rel = function(el){
|
328
|
-
ctl=el.parentNode;
|
329
|
-
container=ctl.parentNode;
|
330
|
-
node=ctl.cloneNode(true);
|
331
|
-
node.getElementsByTagName('input')[0].removeAttribute('disabled');
|
332
|
-
if(container.lastChild==ctl) container.appendChild(node);
|
333
|
-
else container.insertBefore(node, ctl.nextSibling);
|
334
|
-
if(container.childNodes.length>1) container.getElementsByTagName('input')[0].disabled='';
|
335
|
-
}
|
336
|
-
</script>
|
337
|
-
}
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
# The controls map.
|
342
|
-
|
343
|
-
class Control
|
344
|
-
|
345
|
-
# Setup the mapping of names => control classes
|
346
|
-
|
347
|
-
setting :map, :doc => 'Mappings of control names => classes', :default => {
|
348
|
-
:fixnum => FixnumControl,
|
349
|
-
:integer => FixnumControl,
|
350
|
-
:float => FloatControl,
|
351
|
-
:boolean => CheckboxControl,
|
352
|
-
:checkbox => CheckboxControl,
|
353
|
-
:string => TextControl,
|
354
|
-
:password => PasswordControl,
|
355
|
-
:textarea => TextareaControl,
|
356
|
-
:true_class => CheckboxControl,
|
357
|
-
:array => ArrayControl,
|
358
|
-
:refers_to => RefersToControl,
|
359
|
-
:has_one => RefersToControl,
|
360
|
-
:belongs_to => RefersToControl,
|
361
|
-
:has_many => HasManyControl,
|
362
|
-
:many_to_many => HasManyControl,
|
363
|
-
:joins_many => HasManyControl
|
364
|
-
}
|
365
|
-
|
366
|
-
# Fetch a control, setup for 'obj' for a property, or relation
|
367
|
-
# or :symbol defaults to Control if not found
|
368
|
-
|
369
|
-
def self.fetch(obj, key, options={})
|
370
|
-
if key.kind_of? Og::Relation
|
371
|
-
control_sym = key[:control] || key.class.to_s.demodulize.underscore.to_sym
|
372
|
-
elsif key.kind_of? Property
|
373
|
-
control_sym = key[:control] || key.klass.to_s.underscore.to_sym
|
374
|
-
else
|
375
|
-
control_sym = key.to_sym
|
376
|
-
end
|
377
|
-
|
378
|
-
default_to = options.fetch(:default, Control)
|
379
|
-
self.map.fetch(control_sym, Control).new(obj, key, options)
|
380
|
-
end
|
381
|
-
|
382
|
-
end
|
383
|
-
|
384
|
-
end
|
385
|
-
|
386
|
-
end
|
387
|
-
|
388
|
-
# * George Moschovitis <gm@navel.gr>
|
389
|
-
# * Chris Farmiloe <chris.farmiloe@farmiloe.com>
|