nitro 0.25.0 → 0.26.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/CHANGELOG +531 -1
  2. data/ProjectInfo +29 -5
  3. data/README +1 -1
  4. data/doc/AUTHORS +12 -6
  5. data/doc/RELEASES +114 -0
  6. data/lib/glue/sweeper.rb +71 -0
  7. data/lib/nitro.rb +19 -12
  8. data/lib/nitro/adapter/cgi.rb +4 -0
  9. data/lib/nitro/adapter/webrick.rb +4 -2
  10. data/lib/nitro/caching.rb +1 -0
  11. data/lib/nitro/caching/fragments.rb +7 -1
  12. data/lib/nitro/caching/output.rb +6 -1
  13. data/lib/nitro/caching/stores.rb +13 -1
  14. data/lib/nitro/cgi.rb +9 -1
  15. data/lib/nitro/cgi/request.rb +11 -3
  16. data/lib/nitro/cgi/utils.rb +24 -2
  17. data/lib/nitro/compiler.rb +89 -63
  18. data/lib/nitro/compiler/cleanup.rb +16 -0
  19. data/lib/nitro/compiler/elements.rb +117 -0
  20. data/lib/nitro/compiler/markup.rb +3 -1
  21. data/lib/nitro/compiler/morphing.rb +203 -73
  22. data/lib/nitro/compiler/script_generator.rb +14 -0
  23. data/lib/nitro/compiler/shaders.rb +1 -1
  24. data/lib/nitro/context.rb +5 -6
  25. data/lib/nitro/controller.rb +43 -21
  26. data/lib/nitro/dispatcher.rb +86 -37
  27. data/lib/nitro/element.rb +3 -105
  28. data/lib/nitro/helper/benchmark.rb +3 -0
  29. data/lib/nitro/helper/dojo.rb +0 -0
  30. data/lib/nitro/helper/form.rb +85 -255
  31. data/lib/nitro/helper/form/controls.rb +274 -0
  32. data/lib/nitro/helper/javascript.rb +86 -6
  33. data/lib/nitro/helper/pager.rb +5 -0
  34. data/lib/nitro/helper/prototype.rb +49 -0
  35. data/lib/nitro/helper/scriptaculous.rb +0 -0
  36. data/lib/nitro/helper/xhtml.rb +11 -8
  37. data/lib/nitro/helper/xml.rb +1 -1
  38. data/lib/nitro/routing.rb +8 -1
  39. data/lib/nitro/scaffolding.rb +344 -0
  40. data/lib/nitro/server.rb +5 -1
  41. data/lib/nitro/server/runner.rb +19 -15
  42. data/lib/nitro/session.rb +32 -56
  43. data/lib/nitro/session/drbserver.rb +1 -1
  44. data/lib/nitro/session/file.rb +34 -15
  45. data/lib/nitro/session/memory.rb +13 -4
  46. data/lib/nitro/session/og.rb +56 -0
  47. data/proto/public/js/controls.js +30 -1
  48. data/proto/public/js/dragdrop.js +211 -146
  49. data/proto/public/js/effects.js +261 -399
  50. data/proto/public/js/prototype.js +131 -72
  51. data/proto/public/scaffold/edit.xhtml +10 -3
  52. data/proto/public/scaffold/form.xhtml +1 -7
  53. data/proto/public/scaffold/index.xhtml +20 -0
  54. data/proto/public/scaffold/list.xhtml +15 -8
  55. data/proto/public/scaffold/new.xhtml +10 -3
  56. data/proto/public/scaffold/search.xhtml +28 -0
  57. data/proto/public/scaffold/view.xhtml +8 -0
  58. data/proto/run.rb +93 -1
  59. data/src/part/admin.rb +4 -2
  60. data/src/part/admin/controller.rb +62 -28
  61. data/src/part/admin/skin.rb +8 -8
  62. data/src/part/admin/system.css +135 -0
  63. data/src/part/admin/template/index.xhtml +8 -12
  64. data/test/nitro/caching/tc_stores.rb +17 -0
  65. data/test/nitro/tc_caching.rb +1 -4
  66. data/test/nitro/tc_dispatcher.rb +22 -10
  67. data/test/nitro/tc_element.rb +1 -1
  68. data/test/nitro/tc_session.rb +23 -11
  69. data/test/public/blog/another/very_litle/index.xhtml +1 -0
  70. metadata +29 -15
  71. data/lib/nitro/dispatcher/general.rb +0 -62
  72. data/lib/nitro/dispatcher/nice.rb +0 -57
  73. data/lib/nitro/scaffold.rb +0 -171
  74. data/proto/public/index.xhtml +0 -83
  75. data/proto/public/js/scaffold.js +0 -74
  76. data/proto/public/settings.xhtml +0 -66
@@ -6,32 +6,19 @@ require 'nitro/helper/default'
6
6
 
7
7
  module Nitro
8
8
 
9
- # The Dispatcher manages a set of controllers.
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 Dispatcher
12
-
13
- include Router
12
+ class NoActionError < NoMethodError; end
14
13
 
15
- # The dispatcher specialization used.
14
+ # The Dispatcher manages a set of controllers. It maps
15
+ # a request uri to a [controller, action] pair.
16
16
 
17
- setting :mode, :default => :nice, :doc => 'The dispatcher specialization used'
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
- attr_accessor :public_root
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
- if route = c.ann(:m).route and (!route.nil?)
142
- unless c.ann(:m).params.nil?
143
- keys = c.ann(:m).params.keys
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}/#{action}", *keys]
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
- # Specialize in your application or use the default
169
- # specializations in lib/nitro/dispatcher/*
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>
@@ -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
@@ -1,42 +1,75 @@
1
1
  require 'nano/inflect'
2
2
 
3
- require 'nitro/helper/xhtml'
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
- # FIXME: cleanup this file, move stuff to scaffold, allow
12
- # overrides.
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
- private
23
-
24
- # Propagate the given parameter by using a form hidden field. The
25
- # parameter value is tus passed to the next request.
26
-
27
- def propagate_param(param)
28
- %|<input type="hidden" name="#{param}" value="#{request[param.to_s]}" />|
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
- alias_method :keep_param, :propagate_param
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
- # TODO: get info, for example localization mode from session,
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 pr in obj.class.properties.values
50
- if pr.klass.ancestors.include? Og::Blob
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 << tags_for(obj, options)
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
- if obj.class.relations and not options[:skip_relations]
130
- for rel in obj.class.relations
131
- next if rel.options[:editor] == :none unless options[:all]
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 << epilogue()
106
+ controls_for_properties(str, obj, options)
107
+ controls_for_relations(str, obj, options)
155
108
 
156
- return str
157
- end
158
-
159
- def hash_tag(obj, p, options)
160
- str = %{
161
- <input type="text" id="#{p.symbol}_key" /> =&gt; <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" /> =&gt; <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}/>&nbsp; }
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
- # :section: Relations.
114
+ # Render the controls for the properties of the given object.
211
115
 
212
- def refers_to_control(obj, rel, options)
213
- entities = rel.target_class.all
214
- selected = obj.send(rel.name)
215
- selected = selected.pk if selected
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 &gt;&gt;</a>
272
- <a href="javascript:multiSelectorMove('#{rel.name}','remove');">&lt;&lt; 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
- str << %{</div>}
282
- else
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
- # :section: General formating methods. Override to customize.
126
+ # Render the controls for the relations of the given object.
288
127
 
289
- # Emit form prologue.
290
-
291
- def prologue
292
- '<dl>'
293
- end
294
-
295
- # Emit form epilogue.
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
+