scaffolding_extensions 1.0.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 (52) hide show
  1. data/LICENSE +19 -0
  2. data/README +144 -0
  3. data/contrib/scaffold_associations_tree/README +9 -0
  4. data/contrib/scaffold_associations_tree/bullet.gif +0 -0
  5. data/contrib/scaffold_associations_tree/minus.gif +0 -0
  6. data/contrib/scaffold_associations_tree/plus.gif +0 -0
  7. data/contrib/scaffold_associations_tree/scaffold_associations_tree.css +20 -0
  8. data/contrib/scaffold_associations_tree/scaffold_associations_tree.js +57 -0
  9. data/contrib/scaffold_auto_complete_style/README +8 -0
  10. data/contrib/scaffold_auto_complete_style/auto_complete.css +23 -0
  11. data/contrib/scaffold_form_focus/README +12 -0
  12. data/contrib/scaffold_form_focus/scaffold_form_focus.js +21 -0
  13. data/contrib/scaffold_jquery_autocomplete/README +8 -0
  14. data/contrib/scaffold_jquery_autocomplete/jquery.ui.se_autocomplete.css +22 -0
  15. data/contrib/scaffold_jquery_autocomplete/jquery.ui.se_autocomplete.js +121 -0
  16. data/doc/advanced.txt +154 -0
  17. data/doc/camping.txt +25 -0
  18. data/doc/controller_spec.txt +20 -0
  19. data/doc/conversion.txt +102 -0
  20. data/doc/model_spec.txt +54 -0
  21. data/doc/ramaze.txt +20 -0
  22. data/doc/sinatra.txt +20 -0
  23. data/doc/testing.txt +12 -0
  24. data/lib/scaffolding_extensions.rb +89 -0
  25. data/lib/scaffolding_extensions/controller.rb +79 -0
  26. data/lib/scaffolding_extensions/controller/action_controller.rb +116 -0
  27. data/lib/scaffolding_extensions/controller/camping.rb +150 -0
  28. data/lib/scaffolding_extensions/controller/ramaze.rb +116 -0
  29. data/lib/scaffolding_extensions/controller/sinatra.rb +183 -0
  30. data/lib/scaffolding_extensions/helper.rb +304 -0
  31. data/lib/scaffolding_extensions/jquery_helper.rb +58 -0
  32. data/lib/scaffolding_extensions/meta_controller.rb +337 -0
  33. data/lib/scaffolding_extensions/meta_model.rb +571 -0
  34. data/lib/scaffolding_extensions/model.rb +30 -0
  35. data/lib/scaffolding_extensions/model/active_record.rb +184 -0
  36. data/lib/scaffolding_extensions/model/ardm.rb +47 -0
  37. data/lib/scaffolding_extensions/model/data_mapper.rb +190 -0
  38. data/lib/scaffolding_extensions/overridable.rb +67 -0
  39. data/lib/scaffolding_extensions/prototype_helper.rb +59 -0
  40. data/scaffolds/edit.rhtml +12 -0
  41. data/scaffolds/habtm.rhtml +24 -0
  42. data/scaffolds/index.rhtml +6 -0
  43. data/scaffolds/layout.rhtml +82 -0
  44. data/scaffolds/list.rhtml +13 -0
  45. data/scaffolds/listtable.rhtml +46 -0
  46. data/scaffolds/manage.rhtml +15 -0
  47. data/scaffolds/merge.rhtml +23 -0
  48. data/scaffolds/new.rhtml +5 -0
  49. data/scaffolds/search.rhtml +11 -0
  50. data/scaffolds/show.rhtml +19 -0
  51. data/test/scaffolding_extensions_test.rb +44 -0
  52. metadata +106 -0
@@ -0,0 +1,183 @@
1
+ begin
2
+ require 'erubis'
3
+ ERB = Erubis::Eruby
4
+ rescue
5
+ require 'erb'
6
+ end
7
+ require 'cgi'
8
+
9
+ module ScaffoldingExtensions
10
+ SCAFFOLD_ROOTS={}
11
+ class << self
12
+ private
13
+ # Sinatra doesn't have a default location for models, so assume none
14
+ def model_files
15
+ @model_files ||= []
16
+ end
17
+ end
18
+
19
+ module SinatraHelper
20
+ private
21
+ def u(s)
22
+ CGI.escape(s.to_s)
23
+ end
24
+
25
+ def h(s)
26
+ CGI.escapeHTML(s.to_s)
27
+ end
28
+ end
29
+
30
+ # Instance methods for the anonymous class that acts as a Controller for Sinatra
31
+ module SinatraController
32
+ private
33
+ # Sinatra doesn't provide a suitable flash. You can hack one together using
34
+ # session if you really need it.
35
+ def scaffold_flash
36
+ {}
37
+ end
38
+
39
+ # Proc that redirects to given url
40
+ def scaffold_redirect_to(url)
41
+ env = @sinatra_event.request.env
42
+ host = env['HTTP_HOST'] || "#{env['SERVER_NAME']}#{":#{env['SERVER_PORT']}" if env['SERVER_PORT'] && env['SERVER_PORT'].to_i != 80}"
43
+ Proc.new{redirect("//#{host}#{url}")}
44
+ end
45
+
46
+ # In order to override the default templates, you need to set
47
+ # @scaffold_template_dir and then create a template file inside that
48
+ # to override the template (make sure the default templates are also
49
+ # in this folder). It doesn't support user modifiable layouts,
50
+ # so you'll have to modify the layout.rhtml file in @scaffold_template_dir.
51
+ #
52
+ # This returns a proc that renders the necessary template using a plain
53
+ # text renderer.
54
+ def scaffold_render_template(action, options = {}, render_options = {})
55
+ suffix = options[:suffix]
56
+ suffix_action = "#{action}#{suffix}"
57
+ @scaffold_options ||= options
58
+ @scaffold_suffix ||= suffix
59
+ @scaffold_class ||= @scaffold_options[:class]
60
+ if render_options.include?(:inline)
61
+ use_js = @scaffold_javascript
62
+ text = ERB.new(render_options[:inline]).result(binding)
63
+ Proc.new do
64
+ headers('Content-Type'=>'text/javascript') if use_js
65
+ render(text, :text, :layout=>false)
66
+ end
67
+ else
68
+ @content = ERB.new(File.read(scaffold_path(File.exists?(scaffold_path(suffix_action)) ? suffix_action : action))).result(binding)
69
+ text = ERB.new(File.read(scaffold_path('layout'))).result(binding)
70
+ Proc.new{render(text, :text, :layout=>false)}
71
+ end
72
+ end
73
+
74
+ def scaffold_request_action
75
+ @scaffold_method
76
+ end
77
+
78
+ def scaffold_request_env
79
+ @sinatra_event.request.env
80
+ end
81
+
82
+ def scaffold_request_id
83
+ @sinatra_event.params[:id]
84
+ end
85
+
86
+ def scaffold_request_method
87
+ @scaffold_request_method
88
+ end
89
+
90
+ def scaffold_request_param(v)
91
+ sparams = @sinatra_event.params
92
+ unless param = sparams[v.to_sym]
93
+ param = {}
94
+ sparams.each do |k,value|
95
+ if match = /#{v}\[([^\]]+)\]/.match(k.to_s)
96
+ param[match[1]] = value
97
+ end
98
+ end
99
+ param = nil if param.empty?
100
+ end
101
+ param
102
+ end
103
+
104
+ # You need to enable Camping's session support for this to work,
105
+ # otherwise, this will always be the empty hash. The session data
106
+ # is only used for access control, so if you aren't using
107
+ # scaffold_session_value, it shouldn't matter.
108
+ def scaffold_session
109
+ @sinatra_event.session
110
+ end
111
+
112
+ def scaffold_set_vars(meth, event)
113
+ @scaffold_path = self.class.scaffold_root
114
+ @scaffold_method = meth
115
+ @sinatra_event = event
116
+ end
117
+
118
+ # Treats the id option as special, appending it to the path.
119
+ # Uses the rest of the options as query string parameters.
120
+ def scaffold_url(action, options = {})
121
+ escaped_options = {}
122
+ options.each{|k,v| escaped_options[u(k.to_s)] = u(v.to_s)}
123
+ id = escaped_options.delete('id')
124
+ id = id ? "/#{id}" : ''
125
+ id << "?#{escaped_options.to_a.collect{|k,v| "#{k}=#{v}"}.join('&')}" unless escaped_options.empty?
126
+ "#{@scaffold_path}/#{action}#{id}"
127
+ end
128
+ end
129
+
130
+ # Class methods for Sinatra necessary for Scaffolding Extensions
131
+ module MetaSinatraController
132
+ include ScaffoldingExtensions::MetaController
133
+ attr_accessor :scaffold_root
134
+
135
+ private
136
+ def scaffold_setup_helper
137
+ include ScaffoldingExtensions::Controller
138
+ include ScaffoldingExtensions::SinatraController
139
+ include ScaffoldingExtensions::Helper
140
+ include ScaffoldingExtensions::PrototypeHelper
141
+ include ScaffoldingExtensions::SinatraHelper
142
+ end
143
+ end
144
+
145
+ module TextRenderer
146
+ def render_text(template)
147
+ template
148
+ end
149
+ end
150
+ end
151
+
152
+ include ScaffoldingExtensions::TextRenderer
153
+
154
+ def scaffold(root, model, options = {})
155
+ scaffold_setup(root).send(:scaffold, model, options)
156
+ end
157
+
158
+ def scaffold_all_models(root, options = {})
159
+ scaffold_setup(root).send(:scaffold_all_models, options)
160
+ end
161
+
162
+ def scaffold_habtm(root, model, association)
163
+ scaffold_setup(root).send(:scaffold_habtm, model, association)
164
+ end
165
+
166
+ def scaffold_setup(root)
167
+ unless klass = ScaffoldingExtensions::SCAFFOLD_ROOTS[root]
168
+ klass = ScaffoldingExtensions::SCAFFOLD_ROOTS[root] = Class.new
169
+ klass.send(:extend, ScaffoldingExtensions::MetaSinatraController)
170
+ klass.scaffold_root = root
171
+ [:get, :post].each do |req_meth|
172
+ send(req_meth, "#{klass.scaffold_root}/?:meth?/?:request_id?") do
173
+ meth = params[:meth] ||= 'index'
174
+ params[:id] ||= params[:request_id]
175
+ @controller = klass.new
176
+ raise(ArgumentError, 'Method Not Allowed') if req_meth == :get && @controller.send(:scaffolded_nonidempotent_method?, meth)
177
+ @controller.send(:scaffold_set_vars, meth, self)
178
+ instance_eval(&@controller.send(meth))
179
+ end
180
+ end
181
+ end
182
+ klass
183
+ end
@@ -0,0 +1,304 @@
1
+ module ScaffoldingExtensions
2
+ # Helper methods used by the scaffold templates
3
+ module Helper
4
+ private
5
+ # Return a string containing associated objects and links (if they would work) to pages
6
+ # to manage those objects.
7
+ def scaffold_association_links
8
+ klass = @scaffold_class
9
+ return '' if @scaffold_class.scaffold_associations.empty?
10
+ read_only = @scaffold_associations_readonly
11
+ show_edit = read_only ? :show : :edit
12
+ so = @scaffold_object
13
+ soid = so.scaffold_id
14
+ singular_name = @scaffold_options[:singular_name]
15
+ content = '<h3 class="scaffold_associated_records_header">Associated Records</h3>'
16
+ content << "<ul id='scaffolded_associations_#{singular_name}_#{soid}' class='#{klass.scaffold_association_list_class}'>\n"
17
+ klass.scaffold_associations.each do |association|
18
+ next unless klass.scaffold_show_association_links?(association)
19
+ class_name = klass.scaffold_associated_name(association)
20
+ human_name = klass.scaffold_associated_human_name(association)
21
+ content << "<li>"
22
+ content << scaffold_check_link(human_name, read_only, "manage_#{class_name}")
23
+ content << "\n "
24
+ case klass.scaffold_association_type(association)
25
+ when :one
26
+ associated_record = klass.scaffold_associated_objects(association, so, :session=>scaffold_session)
27
+ content << " - #{scaffold_check_link(associated_record.scaffold_name, false, "#{show_edit}_#{class_name}", :id=>associated_record.scaffold_id) if associated_record}</li>\n"
28
+ next
29
+ when :edit
30
+ content << scaffold_check_link('(associate)', true, "edit_#{singular_name}_#{association}", :id=>soid) unless read_only
31
+ when :new
32
+ associated_params = {}
33
+ klass.scaffold_new_associated_object_values(association, so).each{|key, value| associated_params["#{class_name}[#{key}]"] = value}
34
+ content << scaffold_check_link('(create)', true, "new_#{class_name}", associated_params) unless read_only
35
+ end
36
+ if (records = klass.scaffold_associated_objects(association, so, :session=>scaffold_session)).length > 0
37
+ content << "<ul>\n"
38
+ records.each do |associated|
39
+ content << "<li>#{scaffold_check_link(associated.scaffold_name, false, "#{show_edit}_#{class_name}", :id=>associated.scaffold_id)}</li>\n"
40
+ end
41
+ content << "</ul>\n"
42
+ end
43
+ content << "</li>\n"
44
+ end
45
+ content << "</ul>\n"
46
+ end
47
+
48
+ # Formats the records returned by scaffold autocompleting to be displayed,
49
+ # should be an unordered list. By default uses the scaffold_name and id of
50
+ # the entries as the value.
51
+ def scaffold_auto_complete_result(entries)
52
+ return unless entries
53
+ content = '<ul>'
54
+ entries.collect{|entry| content << "<li>#{h(entry.scaffold_name_with_id)}</li>"}
55
+ content << '</ul>'
56
+ content
57
+ end
58
+
59
+ # Simple button with label text that submits a form to the given url, options are
60
+ # passed to scaffold_form.
61
+ def scaffold_button_to(text, url, options={})
62
+ "#{scaffold_form(url, options)}\n<input type='submit' value='#{text}' />\n</form>"
63
+ end
64
+
65
+ # Simple button with label text that submits a form via Ajax to the given action,
66
+ # options are passed to scaffold_form_remote_tag.
67
+ def scaffold_button_to_remote(text, action, options)
68
+ "#{scaffold_form_remote_tag(action, options)}\n<input type='submit' value=#{text} />\n</form>"
69
+ end
70
+
71
+ # If scaffolding didn't create the action, return the empty string if blank is true
72
+ # and the text itself if it is not. Otherwise, returns a link to the action, options
73
+ # are passed to scaffold_link.
74
+ def scaffold_check_link(text, blank, action, options={})
75
+ scaffolded_method?(action) ? scaffold_link(text, action, options) : (blank ? '' : h(text))
76
+ end
77
+
78
+ # Proc that formats the label and tag in a table row
79
+ def scaffold_default_field_wrapper
80
+ Proc.new{|label, tag| "<tr><td>#{label}</td><td>#{tag}</td></tr>\n"}
81
+ end
82
+
83
+ # Proc that formats each field row inside a table
84
+ def scaffold_default_form_wrapper
85
+ Proc.new{|rows|"<table class='#{@scaffold_class.scaffold_table_class(:form)}'><tbody>\n#{rows.join}</tbody></table>\n"}
86
+ end
87
+
88
+ # Forms an input field for the given field_type.
89
+ #
90
+ # The following field types are recognized:
91
+ # * :text => textarea
92
+ # * :boolean => select box with blank (NULL), True, and False
93
+ # * :association => select box or autocompleteing text box for the association
94
+ # * :submit, :password, :hidden, :file => input tag with matching type
95
+ # * everything else => input tag with type text
96
+ #
97
+ # Options are converted to html attributes, with the following special options:
98
+ # * :value => the value of the tag, which usually will be just an html attribute,
99
+ # but can be the html inside the textarea, or the choice of selection
100
+ # for one of the selection options
101
+ # * :id => if :name is blank, it is also used for :name
102
+ def scaffold_field_tag(field_type, options, object=nil, field=nil, record_name=nil, field_id=nil)
103
+ options[:name] ||= options[:id] if options[:id]
104
+ value = options[:value] || object.scaffold_value(field)
105
+ case field_type
106
+ when :text
107
+ "<textarea #{scaffold_options_to_html(options)}>#{h value.to_s}</textarea>"
108
+ when :boolean
109
+ s = {value=>"selected='selected'"}
110
+ "<select #{scaffold_options_to_html(options)}><option></option><option value='f' #{s[false]}>False</option><option value='t' #{s[true]}>True</option></select>"
111
+ when :association
112
+ klass = object.class
113
+ if klass.scaffold_association_use_auto_complete(field)
114
+ assocated_object = klass.scaffold_associated_objects(field, object, :session=>scaffold_session)
115
+ options[:value] = assocated_object ? assocated_object.scaffold_name_with_id : ''
116
+ scaffold_text_field_tag_with_auto_complete(options[:id], record_name, field, options)
117
+ else
118
+ s = {object.scaffold_value(field_id).to_i=>"selected='selected'"}
119
+ associated_objects = klass.scaffold_association_find_objects(field, :session=>scaffold_session, :object=>object)
120
+ "<select #{scaffold_options_to_html(options)}><option></option>#{associated_objects.collect{|ao| "<option value='#{i = ao.scaffold_id}' #{s[i]}>#{h ao.scaffold_name}</option>"}.join}</select>"
121
+ end
122
+ else
123
+ options[:type] = :text
124
+ case field_type
125
+ when :submit, :password, :hidden, :file
126
+ options[:size] ||= 30 if field_type == :password
127
+ options[:type] = field_type
128
+ when :date, :integer, :float
129
+ options[:size] ||= 10
130
+ else
131
+ options[:size] ||= 30
132
+ end
133
+ options[:value] ||= value
134
+ "<input #{scaffold_options_to_html(options)} />"
135
+ end
136
+ end
137
+
138
+ # Returns an opening form tag for the given url. The following options are
139
+ # used:
140
+ # * :method => the method (:get or :post) to be used (default is :post)
141
+ # * :attributes => extra html attributes for the form tag, as a string
142
+ def scaffold_form(url, options={})
143
+ meth = options.delete(:method) || :post
144
+ "<form action='#{url}' method='#{meth}' #{options[:attributes]}>#{scaffold_token_tag if meth.to_s == 'post'}"
145
+ end
146
+
147
+ # "enctype='multipart/form-data'" if there is a file field in the form, otherwise
148
+ # the empty string.
149
+ def scaffold_form_enctype(column_names)
150
+ klass = @scaffold_class
151
+ column_names.each{|column_name| return "enctype='multipart/form-data'" if klass.scaffold_column_type(column_name) == :file }
152
+ ''
153
+ end
154
+
155
+ # Returns html fragment containing autocompleting text or select boxes to add associated records
156
+ # to the current record, and line items with buttons to remove associated records
157
+ # from the current record.
158
+ def scaffold_habtm_ajax_associations
159
+ klass = @scaffold_class
160
+ return '' unless klass.scaffold_habtm_with_ajax
161
+ sn = @scaffold_options[:singular_name]
162
+ so = @scaffold_object
163
+ soid = so.scaffold_id
164
+ content = "<div class='habtm_ajax_add_associations' id='#{sn}_habtm_ajax_add_associations'>"
165
+ klass.scaffold_habtm_associations.reject{|association| !scaffolded_method?("add_#{association}_to_#{sn}")}.each do |association|
166
+ content << "#{scaffold_form_remote_tag("add_#{association}_to_#{sn}", :id=>soid)}\n#{scaffold_habtm_ajax_tag("#{sn}_#{association}_id", so, sn, association)}\n<input name='commit' type='submit' value='Add #{klass.scaffold_associated_human_name(association).singularize}' /></form>\n"
167
+ end
168
+ content << "</div><div class='habtm_ajax_remove_associations' id='#{sn}_habtm_ajax_remove_associations'><ul id='#{sn}_associated_records_list'>"
169
+ klass.scaffold_habtm_associations.reject{|association| !scaffolded_method?("remove_#{association}_from_#{sn}")}.each do |association|
170
+ klass.scaffold_associated_objects(association, so, :session=>scaffold_session).each do |associated_record|
171
+ content << scaffold_habtm_association_line_item(klass, association, @scaffold_object, associated_record)
172
+ end
173
+ end
174
+ content << '</ul></div>'
175
+ content
176
+ end
177
+
178
+ # Returns an autocompleting text box, or a select box displaying the records for the associated model that
179
+ # are not already associated with this record.
180
+ def scaffold_habtm_ajax_tag(id, record, model_name, association)
181
+ klass = record.class
182
+ if klass.scaffold_association_use_auto_complete(association)
183
+ scaffold_text_field_tag_with_auto_complete(id, model_name, association)
184
+ else
185
+ scaffold_select_tag(id, klass.scaffold_unassociated_objects(association, record, :session=>scaffold_session))
186
+ end
187
+ end
188
+
189
+ # Line item with button for removing the associated record from the current record
190
+ def scaffold_habtm_association_line_item(klass, association, record, associated_record)
191
+ name = klass.scaffold_name
192
+ associated_suffix = klass.scaffold_associated_name(association)
193
+ arid = associated_record.scaffold_id
194
+ rid = record.scaffold_id
195
+ content = "<li id='#{name}_#{rid}_#{association}_#{arid}'>\n"
196
+ content << scaffold_check_link(klass.scaffold_associated_human_name(association), false, "manage_#{associated_suffix}")
197
+ content << " - \n"
198
+ content << scaffold_check_link(associated_record.scaffold_name, false, "edit_#{associated_suffix}", :id=>arid)
199
+ content << "\n"
200
+ content << scaffold_button_to_remote('Remove', "remove_#{association}_from_#{name}", :id=>rid, "#{name}_#{association}_id"=>arid)
201
+ content << "\n</li>\n"
202
+ content
203
+ end
204
+
205
+ # Script tag with javascript included inside a CDATA section
206
+ def scaffold_javascript_tag(javascript)
207
+ "<script type='text/javascript'>\n//<![CDATA[\n#{javascript}\n//]]>\n</script>"
208
+ end
209
+
210
+ # Label for the given html id with the content text
211
+ def scaffold_label(id, text)
212
+ "<label for='#{id}'>#{h text}</label>"
213
+ end
214
+
215
+ # 'a' tag with the content text. action and options are passed to
216
+ # scaffold_url to get the href.
217
+ def scaffold_link(text, action, options={})
218
+ "<a href='#{scaffold_url(action, options)}'>#{h text}</a>"
219
+ end
220
+
221
+ # Returns link to the scaffolded management page for the model if it was created by the scaffolding.
222
+ def scaffold_manage_link
223
+ manage = "manage#{@scaffold_suffix}"
224
+ "<br />#{scaffold_link("Manage #{@scaffold_options[:plural_lc_human_name]}", manage)}" if scaffolded_method?(manage)
225
+ end
226
+
227
+ # A html fragment containing a paragraph stating there were errors for the @scaffold_object
228
+ # and an unordered list with error messages for that object. If there are no errors,
229
+ # returns an empty string.
230
+ def scaffold_model_error_messages
231
+ return '' unless (errors = @scaffold_object.scaffold_error_messages).length > 0
232
+ content = '<p>There were problems with the following fields:</p><ul>'
233
+ errors.each{|msg| content << "<li>msg</li>"}
234
+ content << '</ul>'
235
+ content
236
+ end
237
+
238
+ # An html fragment with all of the given fields for @scaffold_object, suitable for
239
+ # inclusion in a form tag
240
+ def scaffold_model_field_tags(fields)
241
+ klass = @scaffold_class
242
+ object = @scaffold_object
243
+ record_name = @scaffold_options[:singular_name]
244
+ field_wrapper = klass.scaffold_field_wrapper || scaffold_default_field_wrapper
245
+ rows = fields.collect do |field|
246
+ field_id = klass.scaffold_field_id(field)
247
+ label = scaffold_label("#{record_name}_#{field_id}", klass.scaffold_column_name(field))
248
+ options = klass.scaffold_column_options(field).merge(:name=>"#{record_name}[#{field_id}]", :id=>"#{record_name}_#{field_id}")
249
+ field_tag = scaffold_field_tag(klass.scaffold_column_type(field), options, object, field, record_name, field_id)
250
+ field_wrapper.call(label, field_tag)
251
+ end
252
+ (klass.scaffold_form_wrapper || scaffold_default_form_wrapper).call(rows)
253
+ end
254
+
255
+ # Returns an appropriate scaffolded data entry form for the model, with any related error messages.
256
+ # If a block is given, it yields an empty string which should be modified with html to be added
257
+ # inside the form before the submit button.
258
+ def scaffold_model_form(action, fields, &block)
259
+ content = ''
260
+ options = {}
261
+ options[:id] = @scaffold_object.scaffold_id if action=='update'
262
+ <<-END
263
+ #{scaffold_model_error_messages}
264
+ #{scaffold_form(scaffold_url("#{action}#{@scaffold_suffix}", options), :attributes=>scaffold_form_enctype(fields))}
265
+ #{scaffold_model_field_tags(fields)}
266
+ #{(yield content; content) if block_given?}
267
+ <input type='submit' value="#{@scaffold_submit_value || "#{action.capitalize} #{@scaffold_options[:singular_lc_human_name]}"}" />
268
+ </form>
269
+ END
270
+ end
271
+
272
+ # Turns a hash of options into a string of html attributes, html escaping the values
273
+ def scaffold_options_to_html(options)
274
+ options.collect{|k,v| "#{k}=\"#{h v.to_s}\""}.join(' ')
275
+ end
276
+
277
+ # The suffix needed to params that should be lists. The empty string by default.
278
+ def scaffold_param_list_suffix
279
+ ''
280
+ end
281
+
282
+ # A select tag with the provided name for the given collection of items. The options
283
+ # will have the value of scaffold_id and the content of scaffold_name. If multiple is
284
+ # true, creates a multi-select box.
285
+ def scaffold_select_tag(name, collection, multiple = false)
286
+ "<select name='#{name}#{scaffold_param_list_suffix if multiple}' id='#{name}' #{"multiple='multiple'" if multiple}>#{'<option></option>' unless multiple}#{collection.collect{|obj| "<option value='#{i = obj.scaffold_id}' id='#{name}_#{i}'>#{h obj.scaffold_name}</option>"}.join("\n")}</select>"
287
+ end
288
+
289
+ # Text field with scaffold autocompleting. The id is the html id, and the model name and association
290
+ # are passed to scaffold_javascript_autocompleter. The options are passed to scaffold_field_tag.
291
+ def scaffold_text_field_tag_with_auto_complete(id, model_name, association = nil, options = {})
292
+ content = ScaffoldingExtensions.auto_complete_css.dup
293
+ content << scaffold_field_tag(:string, {:value=>'', :id=>id, :class=>'autocomplete'}.merge(options))
294
+ content << scaffold_javascript_autocompleter(id, model_name, association)
295
+ content
296
+ end
297
+
298
+ # A tag for a CSRF protection token. The empty string by default as it
299
+ # is framework dependent.
300
+ def scaffold_token_tag
301
+ ''
302
+ end
303
+ end
304
+ end