scaffolding_extensions 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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