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.
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
+