nitro 0.25.0 → 0.26.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/CHANGELOG +531 -1
- data/ProjectInfo +29 -5
- data/README +1 -1
- data/doc/AUTHORS +12 -6
- data/doc/RELEASES +114 -0
- data/lib/glue/sweeper.rb +71 -0
- data/lib/nitro.rb +19 -12
- data/lib/nitro/adapter/cgi.rb +4 -0
- data/lib/nitro/adapter/webrick.rb +4 -2
- data/lib/nitro/caching.rb +1 -0
- data/lib/nitro/caching/fragments.rb +7 -1
- data/lib/nitro/caching/output.rb +6 -1
- data/lib/nitro/caching/stores.rb +13 -1
- data/lib/nitro/cgi.rb +9 -1
- data/lib/nitro/cgi/request.rb +11 -3
- data/lib/nitro/cgi/utils.rb +24 -2
- data/lib/nitro/compiler.rb +89 -63
- data/lib/nitro/compiler/cleanup.rb +16 -0
- data/lib/nitro/compiler/elements.rb +117 -0
- data/lib/nitro/compiler/markup.rb +3 -1
- data/lib/nitro/compiler/morphing.rb +203 -73
- data/lib/nitro/compiler/script_generator.rb +14 -0
- data/lib/nitro/compiler/shaders.rb +1 -1
- data/lib/nitro/context.rb +5 -6
- data/lib/nitro/controller.rb +43 -21
- data/lib/nitro/dispatcher.rb +86 -37
- data/lib/nitro/element.rb +3 -105
- data/lib/nitro/helper/benchmark.rb +3 -0
- data/lib/nitro/helper/dojo.rb +0 -0
- data/lib/nitro/helper/form.rb +85 -255
- data/lib/nitro/helper/form/controls.rb +274 -0
- data/lib/nitro/helper/javascript.rb +86 -6
- data/lib/nitro/helper/pager.rb +5 -0
- data/lib/nitro/helper/prototype.rb +49 -0
- data/lib/nitro/helper/scriptaculous.rb +0 -0
- data/lib/nitro/helper/xhtml.rb +11 -8
- data/lib/nitro/helper/xml.rb +1 -1
- data/lib/nitro/routing.rb +8 -1
- data/lib/nitro/scaffolding.rb +344 -0
- data/lib/nitro/server.rb +5 -1
- data/lib/nitro/server/runner.rb +19 -15
- data/lib/nitro/session.rb +32 -56
- data/lib/nitro/session/drbserver.rb +1 -1
- data/lib/nitro/session/file.rb +34 -15
- data/lib/nitro/session/memory.rb +13 -4
- data/lib/nitro/session/og.rb +56 -0
- data/proto/public/js/controls.js +30 -1
- data/proto/public/js/dragdrop.js +211 -146
- data/proto/public/js/effects.js +261 -399
- data/proto/public/js/prototype.js +131 -72
- data/proto/public/scaffold/edit.xhtml +10 -3
- data/proto/public/scaffold/form.xhtml +1 -7
- data/proto/public/scaffold/index.xhtml +20 -0
- data/proto/public/scaffold/list.xhtml +15 -8
- data/proto/public/scaffold/new.xhtml +10 -3
- data/proto/public/scaffold/search.xhtml +28 -0
- data/proto/public/scaffold/view.xhtml +8 -0
- data/proto/run.rb +93 -1
- data/src/part/admin.rb +4 -2
- data/src/part/admin/controller.rb +62 -28
- data/src/part/admin/skin.rb +8 -8
- data/src/part/admin/system.css +135 -0
- data/src/part/admin/template/index.xhtml +8 -12
- data/test/nitro/caching/tc_stores.rb +17 -0
- data/test/nitro/tc_caching.rb +1 -4
- data/test/nitro/tc_dispatcher.rb +22 -10
- data/test/nitro/tc_element.rb +1 -1
- data/test/nitro/tc_session.rb +23 -11
- data/test/public/blog/another/very_litle/index.xhtml +1 -0
- metadata +29 -15
- data/lib/nitro/dispatcher/general.rb +0 -62
- data/lib/nitro/dispatcher/nice.rb +0 -57
- data/lib/nitro/scaffold.rb +0 -171
- data/proto/public/index.xhtml +0 -83
- data/proto/public/js/scaffold.js +0 -74
- data/proto/public/settings.xhtml +0 -66
data/lib/nitro/dispatcher.rb
CHANGED
|
@@ -6,32 +6,19 @@ require 'nitro/helper/default'
|
|
|
6
6
|
|
|
7
7
|
module Nitro
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
# Raised when an action can not be found for a path
|
|
10
|
+
# check for this in your error action to catch as if 404
|
|
10
11
|
|
|
11
|
-
class
|
|
12
|
-
|
|
13
|
-
include Router
|
|
12
|
+
class NoActionError < NoMethodError; end
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
# The Dispatcher manages a set of controllers. It maps
|
|
15
|
+
# a request uri to a [controller, action] pair.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
unless const_defined? :ROOT
|
|
20
|
-
ROOT = '/'
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# The public root directory. The files in this
|
|
24
|
-
# directory are published by the web server.
|
|
17
|
+
class Dispatcher
|
|
18
|
+
include Router
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
ROOT = '/'
|
|
27
21
|
|
|
28
|
-
# The template root directory. By default this points to the
|
|
29
|
-
# public root directory to allow for PHP/JSP/ASP style
|
|
30
|
-
# programming. But you should probably change this to
|
|
31
|
-
# another directory for extra security.
|
|
32
|
-
|
|
33
|
-
attr_accessor :template_root
|
|
34
|
-
|
|
35
22
|
# The controllers map.
|
|
36
23
|
|
|
37
24
|
attr_accessor :controllers
|
|
@@ -45,9 +32,6 @@ class Dispatcher
|
|
|
45
32
|
# controller that gets mapped to :root.
|
|
46
33
|
|
|
47
34
|
def initialize(controllers = nil)
|
|
48
|
-
@public_root = 'public'
|
|
49
|
-
@template_root = @public_root
|
|
50
|
-
|
|
51
35
|
if controllers and controllers.is_a?(Class) and controllers.ancestors.include?(Controller)
|
|
52
36
|
controllers = { '/' => controllers }
|
|
53
37
|
else
|
|
@@ -87,12 +71,6 @@ class Dispatcher
|
|
|
87
71
|
|
|
88
72
|
auto_mixin(c)
|
|
89
73
|
|
|
90
|
-
# Perform mount-time initialization of the controller.
|
|
91
|
-
|
|
92
|
-
if c.respond_to? :mounted
|
|
93
|
-
c.mounted(path)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
74
|
# Try to setup a template_root if none is defined:
|
|
97
75
|
|
|
98
76
|
unless c.template_root
|
|
@@ -102,6 +80,12 @@ class Dispatcher
|
|
|
102
80
|
end
|
|
103
81
|
}
|
|
104
82
|
end
|
|
83
|
+
|
|
84
|
+
# Keep the mount point as an annotation.
|
|
85
|
+
|
|
86
|
+
c.ann.self.mount_point = path.gsub(/^\//, '')
|
|
87
|
+
|
|
88
|
+
c.mounted(path) if c.respond_to?(:mounted)
|
|
105
89
|
end
|
|
106
90
|
|
|
107
91
|
(@controllers ||= {}).update(controllers)
|
|
@@ -138,13 +122,14 @@ class Dispatcher
|
|
|
138
122
|
@controllers.each do |base, c|
|
|
139
123
|
base = '' if base == '/'
|
|
140
124
|
for m in c.action_methods
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
125
|
+
m = m.to_sym
|
|
126
|
+
if route = c.ann(m).route and (!route.nil?)
|
|
127
|
+
unless c.ann(m).params.nil?
|
|
128
|
+
keys = c.ann(m).params.keys
|
|
144
129
|
else
|
|
145
130
|
keys = []
|
|
146
131
|
end
|
|
147
|
-
@routes << [route, "#{base}/#{
|
|
132
|
+
@routes << [route, "#{base}/#{m}", *keys]
|
|
148
133
|
end
|
|
149
134
|
end
|
|
150
135
|
end
|
|
@@ -159,17 +144,79 @@ class Dispatcher
|
|
|
159
144
|
#
|
|
160
145
|
# [:context]
|
|
161
146
|
# The dispatching context.
|
|
147
|
+
#
|
|
148
|
+
# The dispatching algorithm handles implicit nice urls.
|
|
149
|
+
# Subdirectories are also supported.
|
|
150
|
+
# Action containing '/' separators look for templates
|
|
151
|
+
# in subdirectories. The '/' char is converted to '__'
|
|
152
|
+
# to find the actual action.
|
|
153
|
+
#
|
|
154
|
+
# Returns the dispatcher class, the action name and the
|
|
155
|
+
# base url. For the root path, the base url is nil.
|
|
162
156
|
#--
|
|
163
157
|
# FIXME: this is a critical method that should be optimized
|
|
164
158
|
# watch out for excessive String creation.
|
|
159
|
+
# TODO: add caching.
|
|
165
160
|
#++
|
|
166
|
-
|
|
161
|
+
|
|
167
162
|
def dispatch(path, context = nil)
|
|
168
|
-
|
|
169
|
-
|
|
163
|
+
path = route(path, context)
|
|
164
|
+
|
|
165
|
+
parts = path.split('/')
|
|
166
|
+
parts.shift # get rid of the leading '/'.
|
|
167
|
+
|
|
168
|
+
if klass = controller_class_for("/#{parts.first}")
|
|
169
|
+
base = "/#{parts.shift}"
|
|
170
|
+
else
|
|
171
|
+
base = nil
|
|
172
|
+
klass = controller_class_for(ROOT)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
idx = 0
|
|
176
|
+
found = false
|
|
177
|
+
|
|
178
|
+
# default to index
|
|
179
|
+
|
|
180
|
+
parts << 'index' if parts.empty?
|
|
181
|
+
|
|
182
|
+
# Try to find the first valid action substring
|
|
183
|
+
|
|
184
|
+
action = ''
|
|
185
|
+
|
|
186
|
+
for part in parts
|
|
187
|
+
action << part
|
|
188
|
+
if klass.respond_to_action_or_template?(action)
|
|
189
|
+
found = true
|
|
190
|
+
break
|
|
191
|
+
end
|
|
192
|
+
action << '__'
|
|
193
|
+
idx += 1
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
if found
|
|
197
|
+
parts.slice!(0, idx + 1)
|
|
198
|
+
else
|
|
199
|
+
#--
|
|
200
|
+
# FIXME: no raise to make testable.
|
|
201
|
+
#++
|
|
202
|
+
raise NoActionError, "No action to dispatch to on #{klass}"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# push any remaining parts of the url onto the query
|
|
206
|
+
# string for use with request
|
|
207
|
+
|
|
208
|
+
unless parts.empty?
|
|
209
|
+
context.headers['QUERY_STRING'] = "#{parts.join(';')};#{context.headers['QUERY_STRING']}"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
base = nil if base == ROOT
|
|
213
|
+
|
|
214
|
+
return klass, "#{action}_action", base
|
|
170
215
|
end
|
|
171
216
|
alias_method :split_path, :dispatch
|
|
172
217
|
|
|
218
|
+
private
|
|
219
|
+
|
|
173
220
|
# Get the controller for the given key.
|
|
174
221
|
# Also handles reloading of controllers.
|
|
175
222
|
|
|
@@ -180,6 +227,7 @@ class Dispatcher
|
|
|
180
227
|
klass.instance_methods.grep(/(_action$)|(_template$)/).each do |m|
|
|
181
228
|
klass.send(:remove_method, m) rescue nil
|
|
182
229
|
end
|
|
230
|
+
klass.compile_scaffolding_code if klass.respond_to?(:compile_scaffolding_code)
|
|
183
231
|
end
|
|
184
232
|
|
|
185
233
|
return klass
|
|
@@ -190,3 +238,4 @@ end
|
|
|
190
238
|
end
|
|
191
239
|
|
|
192
240
|
# * George Moschovitis <gm@navel.gr>
|
|
241
|
+
# * Chris Farmiloe <chris.farmiloe@farmiloe.com>
|
data/lib/nitro/element.rb
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
require 'rexml/document'
|
|
2
|
-
require 'rexml/streamlistener'
|
|
3
|
-
|
|
4
1
|
require 'nano/string/capitalized'
|
|
5
2
|
require 'nano/string/camelize'
|
|
6
3
|
require 'mega/annotation'
|
|
@@ -8,6 +5,9 @@ require 'mega/annotation'
|
|
|
8
5
|
require 'glue/flexob'
|
|
9
6
|
require 'glue/configuration'
|
|
10
7
|
|
|
8
|
+
# load the element compiler here for compatibility?
|
|
9
|
+
#require 'nitro/compiler/elements'
|
|
10
|
+
|
|
11
11
|
module Nitro
|
|
12
12
|
|
|
13
13
|
# A programmatically generated element.
|
|
@@ -99,107 +99,5 @@ class Element
|
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
# Processes a page containing elements.
|
|
103
|
-
|
|
104
|
-
class ElementProcessor # :nodoc: all
|
|
105
|
-
|
|
106
|
-
class Listener # :nodoc: all
|
|
107
|
-
include REXML::StreamListener
|
|
108
|
-
|
|
109
|
-
attr_accessor :buffer
|
|
110
|
-
attr_accessor :stack
|
|
111
|
-
|
|
112
|
-
def initialize
|
|
113
|
-
super
|
|
114
|
-
@buffer = ''
|
|
115
|
-
@stack = []
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
PREFIX_RE = /^#{Element.prefix}:/
|
|
119
|
-
CAPITALIZED_RE = /^[A-Z]/
|
|
120
|
-
|
|
121
|
-
def tag_start(name, attributes)
|
|
122
|
-
# check if the name starts with the element prefix, or
|
|
123
|
-
# is capitalized.
|
|
124
|
-
if name =~ PREFIX_RE or name =~ CAPITALIZED_RE
|
|
125
|
-
name = name.split(':')[1].camelize if name =~ PREFIX_RE
|
|
126
|
-
|
|
127
|
-
obj = Object.const_get(name).new
|
|
128
|
-
|
|
129
|
-
attributes.each do | k, v |
|
|
130
|
-
obj.instance_variable_set("@#{k}", v)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
@stack.push [obj, @buffer, @parent]
|
|
134
|
-
|
|
135
|
-
@buffer = obj._text
|
|
136
|
-
@parent.add_child(obj) if @parent
|
|
137
|
-
|
|
138
|
-
@parent = obj
|
|
139
|
-
else # This is a static element.
|
|
140
|
-
attrs = []
|
|
141
|
-
|
|
142
|
-
attributes.each do | k, v |
|
|
143
|
-
attrs << %|#{k}="#{v}"|
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
attrs = attrs.empty? ? nil : " #{attrs.join(' ')}"
|
|
147
|
-
|
|
148
|
-
@buffer << "<#{name}#{attrs}>"
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def tag_end(name)
|
|
153
|
-
# check if the name starts with the element prefix, or
|
|
154
|
-
# is capitalized.
|
|
155
|
-
if name =~ PREFIX_RE or name =~ CAPITALIZED_RE
|
|
156
|
-
name = name.split(':')[1].camelize if name =~ PREFIX_RE
|
|
157
|
-
obj, @buffer, @parent = @stack.pop
|
|
158
|
-
@buffer << obj.render
|
|
159
|
-
else
|
|
160
|
-
@buffer << "</#{name}>"
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def text(str)
|
|
165
|
-
@buffer << str
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def instruction(name, attributes)
|
|
169
|
-
@buffer << "<?#{name}#{attributes}?>"
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
class << self
|
|
174
|
-
def parse(source)
|
|
175
|
-
self.new.parse(source)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def transform(source)
|
|
179
|
-
self.new.transform(source)
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# Expand the elemens found in source.
|
|
184
|
-
|
|
185
|
-
def transform(source)
|
|
186
|
-
listener = Listener.new
|
|
187
|
-
REXML::Document.parse_stream(source, listener)
|
|
188
|
-
# gmosx, FIXME: optimize this, how?
|
|
189
|
-
# gmosx, FIXME: this is a hack fix, improve.
|
|
190
|
-
listener.buffer.gsub! /<textarea ([^>]*)><\/textarea>/, '<textarea \1>#{}</textarea>'
|
|
191
|
-
listener.buffer.gsub! /<(.*) ([^>]*)><\/\1>/, '<\1 \2 />'
|
|
192
|
-
listener.buffer.gsub! /<(.*)><\/\1>/, '<\1 />'
|
|
193
|
-
return listener.buffer
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# An alias.
|
|
198
|
-
|
|
199
|
-
unless const_defined? :Elements
|
|
200
|
-
Elements = ElementProcessor
|
|
201
|
-
end
|
|
202
|
-
|
|
203
102
|
end
|
|
204
|
-
|
|
205
103
|
# * George Moschovitis <gm@navel.gr>
|
|
@@ -4,6 +4,9 @@ module Nitro
|
|
|
4
4
|
|
|
5
5
|
module BenchmarkHelper
|
|
6
6
|
|
|
7
|
+
# Log real time spent on a task. Example:
|
|
8
|
+
# benchmark "Doing an operation" { operation }
|
|
9
|
+
|
|
7
10
|
def benchmark(message = 'Benchmarking')
|
|
8
11
|
real = Benchmark.realtime { yield }
|
|
9
12
|
Logger.info "#{message}: time = #{'%.5f' % real} ms."
|
|
File without changes
|
data/lib/nitro/helper/form.rb
CHANGED
|
@@ -1,42 +1,75 @@
|
|
|
1
1
|
require 'nano/inflect'
|
|
2
2
|
|
|
3
|
-
require 'nitro/helper/
|
|
4
|
-
require 'og/relation/all'
|
|
3
|
+
require 'nitro/helper/form/controls'
|
|
5
4
|
|
|
6
5
|
module Nitro
|
|
7
6
|
|
|
8
7
|
# A collection of useful helpers for creating and manipulating
|
|
9
8
|
# Forms.
|
|
10
9
|
#--
|
|
11
|
-
#
|
|
12
|
-
#
|
|
10
|
+
# This helper may by applied at run time so try to optimize
|
|
11
|
+
# this.
|
|
13
12
|
#++
|
|
14
13
|
|
|
15
14
|
module FormHelper
|
|
16
|
-
|
|
15
|
+
|
|
17
16
|
def self.included(base)
|
|
18
17
|
super
|
|
19
18
|
base.send :include, XhtmlHelper
|
|
20
19
|
end
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
# Override these methods to customize the rendering.
|
|
22
|
+
#
|
|
23
|
+
# An alternative schema could be:
|
|
24
|
+
#
|
|
25
|
+
# <dl>
|
|
26
|
+
# <dt class="#{prop.symbol}">#{label(prop)}</dt>
|
|
27
|
+
# <dd class="#{prop.symbol}">#{html}</dd>
|
|
28
|
+
# </dl>
|
|
29
|
+
|
|
30
|
+
module FormBuilder
|
|
31
|
+
class << self
|
|
32
|
+
# Emit form prologue.
|
|
33
|
+
|
|
34
|
+
def prologue
|
|
35
|
+
''
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Emit form epilogue.
|
|
39
|
+
|
|
40
|
+
def epilogue
|
|
41
|
+
''
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Emit a label.
|
|
45
|
+
|
|
46
|
+
def label(prop)
|
|
47
|
+
%{<label for="#{prop.name}">#{prop[:title] || prop.name.to_s.humanize}</label>}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Emit a form element.
|
|
51
|
+
|
|
52
|
+
def element(prop, html)
|
|
53
|
+
%{
|
|
54
|
+
<p class="form_#{prop.symbol}">
|
|
55
|
+
<div>#{label(prop)}</div>
|
|
56
|
+
#{html}
|
|
57
|
+
</p>
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
29
61
|
end
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# Render a standard form for the given Object. The object
|
|
33
|
-
# should include attribute annotations.
|
|
62
|
+
|
|
63
|
+
# Render a standard form for the given object.
|
|
34
64
|
#--
|
|
35
|
-
#
|
|
36
|
-
# if this module is mixed in a Render.
|
|
65
|
+
# RETHINK this method.
|
|
37
66
|
#++
|
|
38
|
-
|
|
67
|
+
|
|
39
68
|
def form_for(obj, options = {})
|
|
69
|
+
str = ''
|
|
70
|
+
|
|
71
|
+
name = obj.class.name.underscore
|
|
72
|
+
|
|
40
73
|
method = options.fetch(:method, 'post')
|
|
41
74
|
action = options.fetch(:action, "save_#{obj.class.name.underscore}")
|
|
42
75
|
submit = options.fetch(:submit, 'Save')
|
|
@@ -46,8 +79,8 @@ private
|
|
|
46
79
|
enctype_attribute = " enctype=\"#{enctype}\""
|
|
47
80
|
else
|
|
48
81
|
enctype_attribute = ''
|
|
49
|
-
for
|
|
50
|
-
if
|
|
82
|
+
for prop in obj.class.properties.values
|
|
83
|
+
if prop.klass.ancestors.include? Og::Blob
|
|
51
84
|
enctype_attribute = ' enctype="multipart/form-data"'
|
|
52
85
|
end
|
|
53
86
|
end
|
|
@@ -55,261 +88,58 @@ private
|
|
|
55
88
|
|
|
56
89
|
action = "#{@base}/#{action}" unless action =~ /\//
|
|
57
90
|
|
|
58
|
-
str = %{<form action="#{action}" method="post">}
|
|
59
|
-
|
|
60
|
-
str = ''
|
|
61
|
-
|
|
62
91
|
str << %{<form action="#{action}" method="post"#{enctype_attribute}>}
|
|
63
92
|
str << %{<input type="hidden" name="oid" value="#{obj.oid}" />} if obj.oid
|
|
64
|
-
str <<
|
|
93
|
+
str << controls_for(obj, options)
|
|
65
94
|
str << %{<input type="submit" value="#{submit}" />}
|
|
66
95
|
str << %{ or <a href="#{cancel}">Cancel</a>} if cancel
|
|
67
96
|
str << %{</form>}
|
|
68
97
|
|
|
69
|
-
return str
|
|
98
|
+
return str
|
|
70
99
|
end
|
|
71
|
-
|
|
72
|
-
# Render a standard form tags for the given Object. The object
|
|
73
|
-
# should include attribute annotations.
|
|
74
|
-
#
|
|
75
|
-
# If show_all is false then apply field filtering.
|
|
76
|
-
#
|
|
77
|
-
# For extra flexibility and to keep semantics this helper
|
|
78
|
-
# emits a <dl> structure. You can use CSS to style the
|
|
79
|
-
# list to fit your overal design.
|
|
80
|
-
#
|
|
81
|
-
# Example:
|
|
82
|
-
#
|
|
83
|
-
# <p>
|
|
84
|
-
# <form name="test">
|
|
85
|
-
# #{tags_for entry}
|
|
86
|
-
# </form>
|
|
87
|
-
# </p>
|
|
88
|
-
|
|
89
|
-
def tags_for(obj, options = {})
|
|
90
|
-
str = prologue()
|
|
91
|
-
|
|
92
|
-
for p in obj.class.properties.values
|
|
93
|
-
unless options[:all]
|
|
94
|
-
next if :oid == p.symbol or p.editor == :none #TODO check for real key
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
ancestors = p.klass.ancestors
|
|
98
|
-
|
|
99
|
-
tag = if p.editor and obj.respond_to?(p.editor.to_sym)
|
|
100
|
-
obj.send(p.editor.to_sym, p, options) #farms: send to custom _instance_ method
|
|
101
|
-
elsif ancestors.include? Numeric and !p.relation
|
|
102
|
-
field_tag(obj, p, options)
|
|
103
|
-
elsif ancestors.include? String
|
|
104
|
-
if p.editor == :textarea
|
|
105
|
-
textarea_tag(obj, p, options)
|
|
106
|
-
else
|
|
107
|
-
field_tag(obj, p, options)
|
|
108
|
-
end
|
|
109
|
-
elsif ancestors.include? TrueClass
|
|
110
|
-
checkbox_tag(obj, p, options)
|
|
111
|
-
elsif ancestors.include? Date
|
|
112
|
-
date_tag(obj, p, options)
|
|
113
|
-
elsif ancestors.include? Time
|
|
114
|
-
datetime_tag(obj, p, options)
|
|
115
|
-
elsif ancestors.include? Og::Blob
|
|
116
|
-
file_tag(obj, p, options)
|
|
117
|
-
elsif ancestors.include? Hash
|
|
118
|
-
hash_tag(obj, p, options)
|
|
119
|
-
else
|
|
120
|
-
nil #no tag
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
unless tag.nil?
|
|
124
|
-
#farms: TODO. way to override the general 'element' and disable per prop
|
|
125
|
-
str << element(p.symbol, tag)
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
100
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
tag = if rel.options[:editor] && obj.respond_to?(rel.options[:editor].to_sym)
|
|
134
|
-
obj.send(rel.options[:editor].to_sym, rel, options) #farms: allow custom relation editors
|
|
135
|
-
elsif rel.kind_of? Og::RefersTo
|
|
136
|
-
refers_to_control(obj, rel, options)
|
|
137
|
-
elsif rel.kind_of? Og::JoinsMany or rel.kind_of? Og::HasMany
|
|
138
|
-
if rel.options[:editor] == :twin_select or rel.options[:editor] == :twin
|
|
139
|
-
many_select_control(obj, rel, options)
|
|
140
|
-
else
|
|
141
|
-
many_list_control(obj, rel, options)
|
|
142
|
-
end
|
|
143
|
-
else
|
|
144
|
-
nil #no tag
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
unless tag.nil?
|
|
148
|
-
#farms: TODO. way to override the general 'element' and disable per rel
|
|
149
|
-
str << element(rel.name, tag)
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
end
|
|
101
|
+
# Render the controls for the given object.
|
|
102
|
+
|
|
103
|
+
def controls_for(obj, options = {})
|
|
104
|
+
str = FormBuilder.prologue
|
|
153
105
|
|
|
154
|
-
str
|
|
106
|
+
controls_for_properties(str, obj, options)
|
|
107
|
+
controls_for_relations(str, obj, options)
|
|
155
108
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def hash_tag(obj, p, options)
|
|
160
|
-
str = %{
|
|
161
|
-
<input type="text" id="#{p.symbol}_key" /> => <input type="text" id="#{p.symbol}_value" />
|
|
162
|
-
<input id="#{p.symbol}" class="naction_add_hash" type="button" value=" + " />
|
|
163
|
-
<ul id="#{p.symbol}_list">
|
|
164
|
-
}
|
|
165
|
-
obj.send(p.symbol).each do |k,v|
|
|
166
|
-
str << hash_tag_item(p.symbol, k, v)
|
|
167
|
-
end
|
|
168
|
-
str << %{</ul>}
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def hash_tag_item(name, key='', value='')
|
|
172
|
-
%{
|
|
173
|
-
<li>
|
|
174
|
-
<input type="text" value="#{key}" disabled="disabled" class="hash_item_key" /> => <input type="text" name="#{name}[#{key}]" id="#{name}_value" value="#{value}" class="hash_item_value" />
|
|
175
|
-
<span class="remove"><a href="#" class="naction_remove_hash" id="#{name}_remove">remove</a></span>
|
|
176
|
-
</li>
|
|
177
|
-
}
|
|
178
|
-
end
|
|
179
|
-
public :hash_tag_item # Allow ajax access
|
|
180
|
-
|
|
181
|
-
def file_tag(obj, p, options)
|
|
182
|
-
%{<input type="file" id="#{p.symbol}" name="#{p.symbol}" style="width: 250px" />}
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def field_tag(obj, p, options)
|
|
186
|
-
%{<input type="text" id="#{p.symbol}" name="#{p.symbol}" value="#{obj.send(p.symbol)}" style="width: 250px" /> }
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def textarea_tag(obj, p, options)
|
|
190
|
-
%{<textarea id="#{p.symbol}" name="#{p.symbol}">#{obj.send(p.symbol)}</textarea> }
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def checkbox_tag(obj, p, options)
|
|
194
|
-
checked_flag = obj.send(p.symbol) ? 'checked="checked" ' : ''
|
|
195
|
-
%{<input type="checkbox" id="#{p.symbol}" name="#{p.symbol}" #{checked_flag}/> }
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def date_tag(obj, p, options)
|
|
199
|
-
date_select(obj.send(p.symbol), :name => p.symbol.to_s)
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def datetime_tag(obj, p, options)
|
|
203
|
-
datetime_select(obj.send(p.symbol), :name => p.symbol.to_s)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def label(sym)
|
|
207
|
-
%{<label for="#{sym}">#{sym.to_s.humanize}</label>}
|
|
109
|
+
str << FormBuilder.epilogue
|
|
110
|
+
|
|
111
|
+
return str
|
|
208
112
|
end
|
|
209
113
|
|
|
210
|
-
#
|
|
114
|
+
# Render the controls for the properties of the given object.
|
|
211
115
|
|
|
212
|
-
def
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
unless entities.empty?
|
|
217
|
-
str = %{<select id="#{rel.name}" name="#{rel.name}">}
|
|
218
|
-
str << %{<option value="null">None</option>} if rel.options[:allow_none]
|
|
219
|
-
str << %{#{options(:labels => entities.map{|e| e.to_s}, :values => entities.map{|e| e.oid}, :selected => selected)}</select>}
|
|
220
|
-
else
|
|
221
|
-
'No entities found.<br /><br />'
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def many_list_control(obj, rel, options)
|
|
226
|
-
entities = rel.target_class.all
|
|
227
|
-
|
|
228
|
-
unless entities.empty?
|
|
229
|
-
selected_entities = obj.send(rel.name)
|
|
230
|
-
selector_entities = entities.delete_if {|e| obj.saved? && selected_entities.include?(e)}
|
|
231
|
-
|
|
232
|
-
str = %{
|
|
233
|
-
<select id="#{rel.name}_selector">
|
|
234
|
-
#{options(:labels => selector_entities.map{|e| e.to_s}, :values => selector_entities.map{|e| e.oid})}
|
|
235
|
-
</select>
|
|
236
|
-
<input id="#{rel.name}" type="button" value=" + " class="naction_add_rel" />
|
|
237
|
-
<ul class="relation_list" id="#{rel.name}_list">
|
|
238
|
-
}
|
|
239
|
-
selected_entities.each do |e|
|
|
240
|
-
str << relation_item(rel.name, e.to_s, e.oid)
|
|
241
|
-
end
|
|
242
|
-
str << %{</ul>}
|
|
243
|
-
else
|
|
244
|
-
'No entities found.<br /><br />'
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def relation_item(name, display, oid)
|
|
249
|
-
%{
|
|
250
|
-
<li>
|
|
251
|
-
<input type="hidden" name="#{name}[]" value="#{oid}" />
|
|
252
|
-
<span class="display">#{display}</span>
|
|
253
|
-
<span class="remove"><a href="#" id="#{name}_remove" class="naction_remove_rel">remove</a></span>
|
|
254
|
-
</li>
|
|
255
|
-
}
|
|
256
|
-
end
|
|
257
|
-
public :relation_item # Allow ajax access.
|
|
258
|
-
|
|
259
|
-
def many_select_control(obj, rel, options)
|
|
260
|
-
entities = rel.target_class.all
|
|
261
|
-
|
|
262
|
-
unless entities.empty?
|
|
263
|
-
selected_entities = obj.send(rel.name)
|
|
264
|
-
selector_entities = entities.delete_if {|e| obj.saved? && selected_entities.include?(e)}
|
|
265
|
-
str = %{
|
|
266
|
-
<div id="#{rel.name}" class="has_many_control">
|
|
267
|
-
<select id="#{rel.name}_selector" multiple="multiple">
|
|
268
|
-
#{options(:labels => selector_entities.map{|e| e.to_s}, :values => selector_entities.map{|e| e.oid})}
|
|
269
|
-
</select>
|
|
270
|
-
|
|
271
|
-
<a href="javascript:multiSelectorMove('#{rel.name}','add');">add >></a>
|
|
272
|
-
<a href="javascript:multiSelectorMove('#{rel.name}','remove');"><< remove</a>
|
|
273
|
-
|
|
274
|
-
<select id="#{rel.name}_selected" multiple="multiple">
|
|
275
|
-
#{options(:labels => selected_entities.map{|e| e.to_s}, :values => selected_entities.map{|e| e.oid})}
|
|
276
|
-
</select>
|
|
277
|
-
}
|
|
278
|
-
selected_entities.each do |e|
|
|
279
|
-
str << %{<input type="hidden" name="#{rel.name}[]" id="#{rel.name}_#{e.oid}" value="#{e.oid}" />}
|
|
116
|
+
def controls_for_properties(str, obj, options = {})
|
|
117
|
+
for prop in obj.class.properties.values
|
|
118
|
+
unless options[:all]
|
|
119
|
+
next if prop.symbol == obj.class.primary_key.symbol or prop[:control] == :none or prop[:relation]
|
|
280
120
|
end
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
'No entities found.<br /><br />'
|
|
121
|
+
control = Control.fetch(obj,prop).render
|
|
122
|
+
str << FormBuilder.element(prop, control)
|
|
284
123
|
end
|
|
285
124
|
end
|
|
286
125
|
|
|
287
|
-
#
|
|
126
|
+
# Render the controls for the relations of the given object.
|
|
288
127
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def epilogue
|
|
298
|
-
'</dl>'
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
# Emit a form element.
|
|
302
|
-
|
|
303
|
-
def element(name, tag)
|
|
304
|
-
%{
|
|
305
|
-
<dt class="#{name}">#{label(name)}</dt>
|
|
306
|
-
<dd class="#{name}">#{tag}</dd>
|
|
307
|
-
}
|
|
128
|
+
def controls_for_relations(str, obj, options = {})
|
|
129
|
+
for rel in obj.class.relations
|
|
130
|
+
unless options[:all]
|
|
131
|
+
next if rel[:control] == :none
|
|
132
|
+
end
|
|
133
|
+
control = Control.fetch(obj,rel).render
|
|
134
|
+
str << FormBuilder.element(rel, control)
|
|
135
|
+
end
|
|
308
136
|
end
|
|
137
|
+
|
|
309
138
|
end
|
|
310
139
|
|
|
311
|
-
end
|
|
140
|
+
end
|
|
312
141
|
|
|
313
142
|
# * George Moschovitis <gm@navel.gr>
|
|
314
143
|
# * Chris Farmiloe <chris.farmiloe@farmiloe.com>
|
|
315
|
-
# * Rob Pitt <rob@motionpath.co.uk>
|
|
144
|
+
# * Rob Pitt <rob@motionpath.co.uk>
|
|
145
|
+
|