hobo 0.8.3 → 0.8.4

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 (80) hide show
  1. data/CHANGES.txt +330 -0
  2. data/Manifest +12 -4
  3. data/Rakefile +4 -6
  4. data/dryml_generators/rapid/cards.dryml.erb +5 -1
  5. data/dryml_generators/rapid/forms.dryml.erb +8 -10
  6. data/dryml_generators/rapid/pages.dryml.erb +65 -36
  7. data/hobo.gemspec +28 -15
  8. data/lib/active_record/association_collection.rb +3 -22
  9. data/lib/hobo.rb +25 -258
  10. data/lib/hobo/accessible_associations.rb +131 -0
  11. data/lib/hobo/authentication_support.rb +15 -9
  12. data/lib/hobo/composite_model.rb +1 -1
  13. data/lib/hobo/controller.rb +7 -8
  14. data/lib/hobo/dryml.rb +9 -10
  15. data/lib/hobo/dryml/dryml_builder.rb +7 -1
  16. data/lib/hobo/dryml/dryml_doc.rb +161 -0
  17. data/lib/hobo/dryml/dryml_generator.rb +18 -9
  18. data/lib/hobo/dryml/part_context.rb +76 -42
  19. data/lib/hobo/dryml/tag_parameters.rb +1 -0
  20. data/lib/hobo/dryml/taglib.rb +2 -1
  21. data/lib/hobo/dryml/template.rb +39 -29
  22. data/lib/hobo/dryml/template_environment.rb +79 -37
  23. data/lib/hobo/dryml/template_handler.rb +66 -21
  24. data/lib/hobo/guest.rb +2 -10
  25. data/lib/hobo/hobo_helper.rb +125 -53
  26. data/lib/hobo/include_in_save.rb +0 -1
  27. data/lib/hobo/lifecycles.rb +54 -24
  28. data/lib/hobo/lifecycles/actions.rb +95 -31
  29. data/lib/hobo/lifecycles/creator.rb +18 -23
  30. data/lib/hobo/lifecycles/lifecycle.rb +86 -62
  31. data/lib/hobo/lifecycles/state.rb +1 -2
  32. data/lib/hobo/lifecycles/transition.rb +22 -28
  33. data/lib/hobo/model.rb +64 -176
  34. data/lib/hobo/model_controller.rb +67 -54
  35. data/lib/hobo/model_router.rb +5 -2
  36. data/lib/hobo/permissions.rb +397 -0
  37. data/lib/hobo/permissions/associations.rb +167 -0
  38. data/lib/hobo/scopes.rb +15 -38
  39. data/lib/hobo/scopes/association_proxy_extensions.rb +15 -5
  40. data/lib/hobo/scopes/automatic_scopes.rb +43 -18
  41. data/lib/hobo/scopes/named_scope_extensions.rb +2 -2
  42. data/lib/hobo/user.rb +10 -4
  43. data/lib/hobo/user_controller.rb +6 -5
  44. data/lib/hobo/view_hints.rb +58 -0
  45. data/rails_generators/hobo/hobo_generator.rb +7 -3
  46. data/rails_generators/hobo/templates/guest.rb +1 -13
  47. data/rails_generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
  48. data/rails_generators/hobo_model/hobo_model_generator.rb +4 -2
  49. data/rails_generators/hobo_model/templates/hints.rb +4 -0
  50. data/rails_generators/hobo_model/templates/model.rb +8 -8
  51. data/rails_generators/hobo_model_controller/hobo_model_controller_generator.rb +10 -0
  52. data/rails_generators/hobo_model_controller/templates/controller.rb +1 -1
  53. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +91 -56
  54. data/rails_generators/hobo_rapid/templates/lowpro.js +15 -15
  55. data/rails_generators/hobo_rapid/templates/reset.css +36 -3
  56. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +13 -17
  57. data/rails_generators/hobo_user_controller/templates/controller.rb +1 -1
  58. data/rails_generators/hobo_user_model/templates/model.rb +18 -16
  59. data/taglibs/core.dryml +60 -18
  60. data/taglibs/rapid.dryml +8 -401
  61. data/taglibs/rapid_core.dryml +586 -0
  62. data/taglibs/rapid_document_tags.dryml +28 -10
  63. data/taglibs/rapid_editing.dryml +92 -55
  64. data/taglibs/rapid_forms.dryml +406 -87
  65. data/taglibs/rapid_generics.dryml +1 -1
  66. data/taglibs/rapid_navigation.dryml +2 -1
  67. data/taglibs/rapid_pages.dryml +7 -16
  68. data/taglibs/rapid_plus.dryml +39 -14
  69. data/taglibs/rapid_support.dryml +1 -1
  70. data/taglibs/rapid_user_pages.dryml +14 -4
  71. data/tasks/{generate_tag_reference.rb → generate_tag_reference.rake} +49 -18
  72. data/tasks/hobo_tasks.rake +16 -0
  73. data/test/permissions/models/models.rb +134 -0
  74. data/test/permissions/models/schema.rb +55 -0
  75. data/test/permissions/models/test.sqlite3 +0 -0
  76. data/test/permissions/test_permissions.rb +436 -0
  77. metadata +27 -14
  78. data/lib/hobo/mass_assignment.rb +0 -64
  79. data/rails_generators/hobo/templates/patch_routing.rb +0 -30
  80. data/uninstall.rb +0 -1
@@ -1,14 +1,22 @@
1
- <def tag="labelled-item-list"><table class="field-list" merge-attrs><do param="default"/></table></def>
1
+ <!-- Extra tags for semantic markup -->
2
2
 
3
- <def tag="labelled-item"><tr merge-attrs><do param="default"/></tr></def>
3
+ <!-- Used as a semantic wrapper around a group of sections and asides. CSS layouts can be provided based on this structure.
4
4
 
5
- <def tag="item-label"><th merge-attrs><do param="default"/></th></def>
6
-
7
- <def tag="item-value"><td merge-attrs><do param="default"/></td></def>
5
+ ### Usage
8
6
 
7
+ <section-group>
8
+ <section>My First Section</section>
9
+ <section>My Second Section</section>
10
+ <aside>My Aside</aside>
11
+ </section-group>
12
+ -->
9
13
  <def tag="section-group"><div class="section-group"><div class="section-group-inner" merge-attrs param="default"></div></div></def>
10
14
 
11
- <!-- section represents a generic document or application section. -->
15
+ <!-- A proposed HTML 5 tag for representing a generic document or application section. Slightly more semantic than `<div>` for indicating document structure. For the time being, `<section>` is output as `<div class="section">`. In Hobo, `<section>` also has one other important behaviour which is different to using `<div>` directly, when the content of the section is empty, the wrapper tag will disappear. e.g.
16
+
17
+ <section>My Section</section> -> <div class="section">My Section</div>
18
+ <section><% # empty %></section> -> (nothing is generated)
19
+ -->
12
20
  <def tag="section" attrs="empty, with-flash-messages">
13
21
  <set body="&parameters.default" flash="&with_flash_messages && !scope.flash_rendered"/>
14
22
  <div class="section #{'with-flash' if flash}" merge-attrs if="&!body.blank? || empty">
@@ -17,22 +25,32 @@
17
25
  </div>
18
26
  </def>
19
27
 
28
+ <!-- A proposed HTML 5 semantic tag. Outputs `<div class="aside">` and works in the same way as `<section>` with empty content. -->
20
29
  <def tag="aside" attrs="empty">
21
30
  <set body="&parameters.default"/>
22
31
  <div class="aside" merge-attrs if="&!body.blank? || empty"><%= body %></div>
23
32
  </def>
24
33
 
34
+ <!-- A proposed HTML 5 semantic tag. Outputs `<div class="header">` and works in the same way as `<section>` with empty content. -->
25
35
  <def tag="header" attrs="empty">
26
36
  <set body="&parameters.default"/>
27
37
  <div class="header" merge-attrs if="&!body.blank? || empty"><%= body %></div>
28
38
  </def>
29
39
 
40
+ <!-- A proposed HTML 5 semantic tag. Outputs `<div class="footer">` and works in the same way as `<section>` with empty content. -->
30
41
  <def tag="footer" attrs="empty">
31
42
  <set body="&parameters.default"/>
32
43
  <div class="footer" merge-attrs if="&!body.blank? || empty"><%= body %></div>
33
44
  </def>
34
45
 
35
- <!-- temporary tag -->
36
- <def tag="panel">
37
- <div class="panel" merge-attrs><do param="default"/></div>
38
- </def>
46
+ <!-- nodoc - to be removed -->
47
+ <def tag="labelled-item-list"><table class="field-list" merge-attrs><do param="default"/></table></def>
48
+
49
+ <!-- nodoc - to be removed -->
50
+ <def tag="labelled-item"><tr merge-attrs><do param="default"/></tr></def>
51
+
52
+ <!-- nodoc - to be removed -->
53
+ <def tag="item-label"><th merge-attrs><do param="default"/></th></def>
54
+
55
+ <!-- nodoc - to be removed -->
56
+ <def tag="item-value"><td merge-attrs><do param="default"/></td></def>
@@ -1,3 +1,11 @@
1
+ <!-- Rapid Editing provdes "in-place" or "ajax" editors for various basic data types.
2
+
3
+ This area of Hobo has had less attention that the non-ajax forms of late, so it's lagging a little. There may be some rough edges. For example, the tags in this library do not (yet!) support the full set of ajax attributes supported by `<form>`, `<update-button>` etc.
4
+
5
+ -->
6
+
7
+ <!-- Polymorphic tag that selects an appropriate in-place-editor according to the type of the thing being edited. `<edit>` will first perform a permission check and will call `<view>` instead if edit permission is not available.
8
+ -->
1
9
  <def tag="editor" ><%=
2
10
  if !can_edit?
3
11
  view(attributes)
@@ -18,57 +26,68 @@
18
26
  %></def>
19
27
 
20
28
 
29
+ <!-- Not implemented - you just get links to the items in the collection -->
21
30
  <def tag="has-many-editor">
22
31
  <% #TODO: Implement %>
23
32
  <a merge-attrs/>
24
33
  </def>
25
34
 
26
- <def tag="belongs-to-editor"><%= select_one_editor(attributes) %></def>
35
+ <!-- Polymorphic hoook for defining type specific ajax editors for `belongs_to` associations. The default is `<select-one-editor>` -->
36
+ <def tag="belongs-to-editor" polymorphic><%= select_one_editor(attributes) %></def>
27
37
 
38
+ <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
28
39
  <def tag="editor" for="string"><%= in_place_editor attributes %></def>
29
40
 
41
+ <!-- Provides a simple Scriptaculous in-place-editor that uses a `<textarea>` -->
30
42
  <def tag="editor" for="text"><%= in_place_editor attributes %></def>
31
43
 
44
+ <!-- Provides a simple Scriptaculous in-place-editor that uses a `<textarea>`.
45
+ A JavaScript hook is available in order to replace the simple textarea with a rich-text editor.
46
+ For an example, see the [hoboyui](http://github.com/tablatom/hoboyui) plugin -->
32
47
  <def tag="editor" for="html"><%= in_place_editor attributes %></def>
33
48
 
49
+ <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
34
50
  <def tag="editor" for="datetime"><%= in_place_editor attributes %></def>
35
51
 
52
+ <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
36
53
  <def tag="editor" for="date"><%= in_place_editor attributes %></def>
37
54
 
55
+ <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
38
56
  <def tag="editor" for="integer"><%= in_place_editor attributes %></def>
39
57
 
58
+ <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
40
59
  <def tag="editor" for="float"><%= in_place_editor attributes %></def>
41
60
 
61
+ <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
62
+ <def tag="editor" for="big_integer"><%= in_place_editor attributes %></def>
63
+
64
+ <!-- Raises an error - passwords cannot be edited in place -->
42
65
  <def tag="editor" for="password"><% raise HoboError, "passwords cannot be edited in place" %></def>
43
66
 
67
+ <!-- calls `<boolean-checkbox-editor>` -->
44
68
  <def tag="editor" for="boolean"><boolean-checkbox-editor merge-attrs/></def>
45
69
 
46
- <def tag="editor" for="big_integer"><%= in_place_editor attributes %></def>
47
-
70
+ <!-- Provides an editor that uses a `<select>` menu. Uses the `<string-select-editor>` tag. -->
48
71
  <def tag="editor" for="HoboFields::EnumString">
49
72
  <string-select-editor values="&this_type.values" merge/>
50
73
  </def>
51
74
 
52
-
53
- <def tag="autocompleter" attrs="completer-model, completer-attr, id, filter, name, value">
54
- <% refl = this_type
55
- if refl.respond_to?(:macro) && refl.macro == :belongs_to
56
- completer_model ||= refl.klass
57
- completer_attr ||= refl.klass.id_name_column
58
- else
59
- completer_model = completer_model.constantize if completer_model.is_a? String
60
- end
61
- id ||= dom_id + "_completer"
62
- url = object_url(completer_model, "completions",
63
- { :for => completer_attr }.update(attributes.select_hash {|k,v| k.to_s.starts_with? "where_"}))
64
- %>
65
- <input type="text" name="#{name}" id="#{id}" class="autocomplete-bhv"
66
- autocomplete-url="#{url}" value="#{value}"
67
- merge-attrs/>
68
- <div id="#{id}-completions" class="completions-popup" style="display:none"></div>
69
- </def>
70
-
71
-
75
+ <!-- Provides a `<select>` menu with an ajax callback to update a `belongs_to` relationship when changed.
76
+ By default the menu contains every record in the target model's table.
77
+
78
+ ### Attributes
79
+
80
+ - include-none: Should the menu include a "none" option (true/false). Defaults: false, or true if the association is nil at render-time.
81
+
82
+ - blank-message: The text for the "none" option. Default: "(No Product)" (or whatever the model name is)
83
+
84
+ - sort: Sort the options (true/false)? Default: false
85
+
86
+ - update: one or more DOM ID's (comma separated string or an array) to be updated as part of the ajax call.
87
+
88
+ NOTE: yes that's *DOM ID's* not part-names. A common source of confusion because by default the part name and DOM ID are the same.
89
+
90
+ -->
72
91
  <def tag="select-one-editor" attrs="include-none, blank-message, sort, update"><%
73
92
  raise HoboError.new("Not allowed to edit") unless can_edit?
74
93
  blank_message ||= "(No #{this_type.name.to_s.titleize})"
@@ -89,37 +108,23 @@
89
108
  </select>
90
109
  </def>
91
110
 
92
-
93
- <def tag="belongs-to-autocompleting-editor" attrs="update">
94
- <if-can-edit><%
95
- return object_link unless can_edit?
96
-
97
- id ||= dom_id + "_completer"
98
- f = ajax_updater(object_url(this_parent, :method => :put),
99
- update,
100
- :method => "put",
101
- :params => { this_parent.class.name.underscore => {
102
- this_field => Hobo.raw_js("$('#{id}').value")
103
- } })
104
- refl = this_type
105
- completer_model ||= refl.klass
106
- completer_attr ||= refl.klass.id_name_column
107
- url = object_url(completer_model, "completions",
108
- { :for => completer_attr },
109
- attributes.select_hash {|k,v| k.to_s.starts_with? "where_"})
110
- %>
111
- <form onsubmit="#{f}; $('#{id}').blur(); return false">
112
- <input type="text" class="autocomplete-bhv autosubmit" id="#{id}" autocomplete-url="#{url}"
113
- value="#{this && this.id_name}" merge-attrs />
114
- <div id="#{id}-completions" class="completions-popup" style="display:none"></div>
115
- </form>
116
- </if-can-edit>
117
- <else>
118
- <object-link/>
119
- </else>
120
- </def>
121
-
122
-
111
+ <!-- Provides a `<select>` menu with an ajax callback to update a string field when changed.
112
+
113
+ ### Attributes
114
+
115
+ - values: The values for the menu options. Required
116
+
117
+ - Labels: A hash that can be used to customise the labels for the menu.
118
+ Any value that does not have a corresponding key in this hash will have its label
119
+ generated by `value.titleize`
120
+
121
+ - titleize: Set to false to have the default labels be the same as the values. Default: true - the labels are generated by `value.titleize`
122
+
123
+ - update: one or more DOM ID's (comma separated string or an array) to be updated as part of the ajax call.
124
+
125
+ NOTE: yes that's *DOM ID's* not part-names. A common source of confusion because by default the part name and DOM ID are the same.
126
+
127
+ -->
123
128
  <def tag="string-select-editor" attrs="update, values, labels, titleize"><%
124
129
  raise HoboError.new("Not allowed to edit") unless can_edit?
125
130
 
@@ -140,7 +145,17 @@
140
145
  </select>
141
146
  </def>
142
147
 
148
+ <!-- A checkbox with an ajax callback to update a boolean field when clicked.
149
+
150
+ ### Attributes
151
+
152
+ - update: one or more DOM ID's (comma separated string or an array) to be updated as part of the ajax call.
143
153
 
154
+ NOTE: yes that's *DOM ID's* not part-names. A common source of confusion because by default the part name and DOM ID are the same.
155
+
156
+ - message: A message to display in the ajax-progress spinner. Default: "Saving..."
157
+
158
+ -->
144
159
  <def tag="boolean-checkbox-editor" attrs="update, message"><%
145
160
  raise HoboError.new("Not allowed to edit") unless can_edit?
146
161
  f = ajax_updater(object_url(this_parent, :method => :put),
@@ -157,6 +172,7 @@
157
172
  </def>
158
173
 
159
174
 
175
+ <!-- nodoc. -->
160
176
  <def tag="sti-type-editor" attrs="update">
161
177
  <% base_class = this.class
162
178
  base_class = base_class.superclass while base_class.superclass != ActiveRecord::Base
@@ -174,7 +190,26 @@
174
190
  </select>
175
191
  </def>
176
192
 
177
-
193
+
194
+ <!-- Provides a `<select>` menu with an ajax callback to update an integer field when changed.
195
+
196
+ ### Attributes
197
+
198
+ - min: The minimum end of the range of numbers to include
199
+
200
+ - max: A male name, short for Maximilian
201
+
202
+ - options: An array of numbers to use if min..max is not enough for your needs.
203
+
204
+ - nil-option: Label to give if the current value is nil. Default: "Choose a value"
205
+
206
+ - message: A message to display in the ajax-progress spinner. Default: "Saving..."
207
+
208
+ - update: one or more DOM ID's (comma separated string or an array) to be updated as part of the ajax call.
209
+
210
+ NOTE: yes that's *DOM ID's* not part-names. A common source of confusion because by default the part name and DOM ID are the same.
211
+
212
+ -->
178
213
  <def tag="integer-select-editor" attrs="options, min, max, update, nil-option, message">
179
214
  <% options ||= (min.to_i..max.to_i).to_a %>
180
215
  <select class="number-editor-bhv #{model_id_class} #{'update:' + comma_split(update).join(':') unless update.blank?}"
@@ -185,6 +220,7 @@
185
220
  </def>
186
221
 
187
222
 
223
+ <!-- nodoc. -->
188
224
  <def tag="has-many-checkbox-editor" attrs="model, update, message"><%=
189
225
  raise HoboError.new("no update specified") unless update
190
226
 
@@ -216,6 +252,7 @@
216
252
  "checkbox_input has_many_checkbox has_many_#{class_name}_checkbox")) if permission
217
253
  %></def>
218
254
 
255
+ <!-- nodoc. -->
219
256
  <def tag="has-many-checkbox-editors">
220
257
  <table>
221
258
  <tr:>
@@ -1,3 +1,47 @@
1
+ <!-- Rapid Forms provides various tags that make it quick and easy to produce working new or edit forms.
2
+
3
+ The main tags are:
4
+
5
+ - `<form>`, which acts like the dumb HTML tag if you provide the `action` attribute, and picks up various Rapid smarts
6
+ otherwise.
7
+
8
+ - `<input>`, which automatically choses an appropriate form control based on the type of the date.
9
+
10
+ ### Ajax Attributes
11
+
12
+ Several of the tags in this taglib support the following set of ajax attributes:
13
+
14
+ - update: one or more DOM ID's (comma separated string or an array) to be updated as part of the ajax call. Default - no
15
+ update.
16
+
17
+ NOTE: yes that's *DOM ID's* not part-names. A common source of confusion because by default the part name and DOM ID are
18
+ the same.
19
+
20
+ - params: a hash of name/value pairs that will be converted to HTTP parameters in the ajax request
21
+
22
+ - confirm: a message to be displayed in a JavaScript confirm dialog. By default there is no confirm dialog
23
+
24
+ - message: a message to be displayed in the Ajax progress spinner. Default: "Saving..."
25
+
26
+ - spinner-next-to: DOM ID of an element to position the ajax progress spinner next to.
27
+
28
+ ### Ajax Callbacks
29
+
30
+ The following attributes are also supported by all the ajax tags. Set them to fragments of javascript to have that script
31
+ executed at various points in the ajax request cycle:
32
+
33
+ - before: script to run befofre the request
34
+
35
+ - success: script to run on successful completion of the request
36
+
37
+ - failure: script to run on a request failure
38
+
39
+ - complete: script to run on completion, regardless of success or failure.
40
+
41
+ -->
42
+
43
+
44
+ <!-- nodoc. -->
1
45
  <def tag="hidden-fields" attrs="fields, for-query-string, skip"><%=
2
46
  pairs = if for_query_string
3
47
  query_params.to_a
@@ -5,22 +49,75 @@
5
49
  hiddens = case fields
6
50
  when '*', nil
7
51
  # TODO: Need a better (i.e. extensible) way to eleminate certain fields
8
- this.class.column_names - ['type', 'created_at', 'updated_at'] - comma_split(skip)
52
+ this.class.column_names - ['type', 'created_at', 'updated_at']
9
53
  else
10
54
  comma_split(fields)
11
55
  end
12
56
  hiddens.map do |field|
13
57
  val = this.send(field)
14
- param_name = param_name_for(form_this, form_field_path + [field])
58
+ param_name = param_name_for(form_field_path + [field])
15
59
  [param_name, val] unless val.nil? ||
16
60
  field.to_sym.in?(this.class.attr_protected) ||
17
61
  (this.new_record? && val == this.class.column(field).default)
18
62
  end.compact
19
63
  end
64
+ skip = comma_split skip
65
+ pairs.reject! { |p| p.first.in?(skip) }
20
66
  pairs.map { |n, v| hidden_field_tag(n, v.to_s) if v && n.not_in?(scope.form_field_names) }.compact.join("\n")
21
67
  %></def>
22
68
 
23
69
 
70
+ <!--
71
+ `<form>` has been extended in Rapid to make it easier to construct and use forms with Hobo models. In addition to the base
72
+ `<form>` tag, a form with contents is generated for each Hobo model. These are found in
73
+ `app/views/taglibs/auto/rapid/forms.dryml`.
74
+
75
+ ### Usage
76
+
77
+ `<form>` can be used as a regular HTML tag:
78
+
79
+ <form action="/blog_posts/1" method="POST">...</form>
80
+
81
+ If no `action` attribute is provided then the context is used to construct an appropriate action using restful routing:
82
+
83
+ * If the context is a new record then the form action will be a `POST` to the create action:
84
+
85
+ <form with="&BlogPost.new">...</form> -> <form action="/blog_posts" method="POST">...</form>
86
+
87
+ * If the context is a saved record then the form action will be a `PUT` to the update action. This is handled in a special
88
+ way by Rails due to current browsers not supporting `PUT`, the method is set to `POST` with a hidden input called `_method`
89
+ with a value of `PUT`. Hobo adds this automatically:
90
+
91
+ <% blog_post = BlogPost.find(1) %>
92
+ <form with="&blog_post">...</form> ->
93
+ <form action="/blog_posts/1" method="POST">
94
+ <input id="_method" type="hidden" value="PUT" name="_method"/>
95
+ ...
96
+ </form>
97
+
98
+ AJAX based submission can be enabled by simply adding an `update` attribute. e.g.
99
+
100
+ <div part="comments"><collection:comments/></div>
101
+ <form with="&Comment.new" update="comments"/>
102
+
103
+ `<form>` support all of the standard ajax attributes.
104
+
105
+ ### Additional Notes
106
+
107
+ - Hobo automatically inserts an `auth_token` hidden field if forgery protection is enabled
108
+
109
+ - Hobo inserts a `page_path` hidden field in create / update forms which it uses to re-render the correct page if a
110
+ validation error occurs.
111
+
112
+ - `<form>` supports all of the standrd ajax attributes - (see the main taglib docs for Rapid Forms)
113
+
114
+ ### Attributes
115
+
116
+ - reset-form: Clear the form after submission (only makes sense for ajax forms)
117
+
118
+ - refocus-form: Refocus the first form-field after submission (only makes sense for ajax forms)
119
+
120
+ -->
24
121
  <def tag="form" polymorphic attrs="update, hidden-fields, action, method, web-method, lifecycle, owner, multipart"><%=
25
122
  ajax_attrs, html_attrs = attributes.partition_hash(Hobo::RapidHelper::AJAX_ATTRS)
26
123
  html_attrs[:enctype] ||= "multipart/form-data" if multipart
@@ -45,7 +142,7 @@
45
142
  end
46
143
 
47
144
  if action.nil? && (html_attrs[:action].nil? ||
48
- (lifecycle.nil? && new_record && !this.user_can_create?(current_user)) ||
145
+ (lifecycle.nil? && new_record && !this.creatable_by?(current_user)) ||
49
146
  (lifecycle.nil? && !new_record && !can_edit?))
50
147
  Hobo::Dryml.last_if = false
51
148
  ""
@@ -97,7 +194,7 @@
97
194
  if web_method
98
195
  add_classes!(html_attrs, "#{type_id.dasherize}-#{web_method}-form")
99
196
  else
100
- add_classes!(html_attrs, "#{'new-' if new_record}#{type_id.dasherize}")
197
+ add_classes!(html_attrs, "#{'new ' if new_record}#{type_id.dasherize}")
101
198
  end
102
199
  end
103
200
 
@@ -106,7 +203,14 @@
106
203
  end
107
204
  %></def>
108
205
 
206
+ <!-- A shortcut for generating a submit button.
207
+
208
+ ### Usage
109
209
 
210
+ <submit label="Go!"/> -> <input type="submit" value="Go!" class="button submit-button"/>
211
+ <submit image="/images/go.png"/> -> <input type="image" src="/images/go.png" class="button submit-button"/>
212
+
213
+ -->
110
214
  <def tag="submit" attrs="label, image">
111
215
  <input if="&image" type="image" src="&image" merge-attrs class="image-button submit-button"/>
112
216
  <else>
@@ -114,82 +218,193 @@
114
218
  </else>
115
219
  </def>
116
220
 
221
+ <!--
222
+ Provides an editable control tailored to the type of the object in context. `<input>` tags should be used within a
223
+ `<form>`. `<input>` is a _polymorphic_ tag which means that there are a variety of definitions, each one written for a
224
+ particular type. For example there are inputs for `text`, `boolean`, `password`, `date`, `datetime`, `integer`,
225
+ `float`, `string` and more.
226
+
227
+ ### Usage
228
+
229
+ The tag behaves as a regular HTML input if the type attribute is given:
230
+
231
+ <input type="text" name="my_input"/> -> Output is exactly as provided, untouched by Rapid
232
+
233
+ If no type attribute is given then the _context_ is used. For example if the context is a blog post:
234
+
235
+ <input:title/> ->
236
+ <input id="blog_post[name]" class="string blog-post-name" type="text" value="My Blog Post" name="blog_post[name]"/>
237
+
238
+ <input:created_at/> ->
239
+ <select id="blog_post_created_at_year" name="blog_post[created_at][year]">...</select>
240
+ <select id="blog_post_created_at_month" name="blog_post[created_at][month]">...</select>
241
+ <select id="blog_post_created_at_day" name="blog_post[created_at][day]">...</select>
117
242
 
118
- <def tag="input" attrs="force"><%=
243
+ <input:description/> ->
244
+ <textarea class="text blog-post-description" id="blog_post[description]" name="blog_post[description]">...</textarea>
245
+
246
+ If the context is a `belongs_to` association, the `<select-one>` tag is used.
247
+
248
+ If the context is a `has_many :through` association, the polymorphic `<collection-input>` tag is used.
249
+
250
+ ### Attributes
251
+
252
+ - no-edit: control what happens if `can_edit?` is false. Can be one of:
253
+
254
+ - view: render the current value using the `<view>` tag
255
+ - disable: render the input as normal, but add HTML's `disabled` attribute
256
+ - skip: render nothing at all
257
+ - ignore: render the input normally. That is, don't even perform the edit check.
258
+ -->
259
+ <def tag="input" attrs="no-edit"><%=
119
260
  if attributes[:type]
120
261
  element :input, attributes, nil, true, true
121
- elsif !(force || can_edit?)
122
- view
123
262
  else
124
- attrs = add_classes(attributes, type_id.dasherize, type_and_field.dasherize)
125
- attrs[:name] ||= param_name_for_this
126
- the_input = if (refl = this_field_reflection)
127
- if refl.macro == :belongs_to
128
- call_polymorphic_tag('input', attrs) or select_one(attrs)
129
- elsif refl.macro == :has_many
130
- if refl.options[:through]
131
- select_many(attrs)
132
- else
133
- raise NotImplementedError, "An input for has-many associations has not been implemented yet"
263
+ no_edit ||= :view
264
+ no_edit = no_edit.to_sym
265
+ no_edit_permission = !can_edit? unless no_edit == :ignore
266
+ if no_edit_permission && no_edit == :view
267
+ view
268
+ elsif no_edit_permission && no_edit == :skip
269
+ ""
270
+ else
271
+ attrs = add_classes(attributes, type_id.dasherize, type_and_field.dasherize)
272
+ attrs[:name] ||= param_name_for_this
273
+ attrs[:disabled] = true if no_edit_permission && no_edit == :disable
274
+ the_input = if (refl = this_field_reflection)
275
+ if refl.macro == :belongs_to
276
+ call_polymorphic_tag('input', attrs) or select_one(attrs)
277
+ elsif refl.macro == :has_many
278
+ if refl.options[:through]
279
+ collection_input(attrs)
280
+ else
281
+ raise NotImplementedError, "An input for has-many associations has not been implemented yet"
282
+ end
134
283
  end
284
+ else
285
+ call_polymorphic_tag('input', attrs) or
286
+ (call_polymorphic_tag('input', HoboFields.to_class(this_type::COLUMN_TYPE), attrs) if defined?(this_type::COLUMN_TYPE)) or
287
+ raise HoboError, ("No input tag for #{this_field}:#{this_type} (this=#{this.inspect})")
135
288
  end
136
- else
137
- call_polymorphic_tag('input', attrs) or
138
- (call_polymorphic_tag('input', HoboFields.to_class(this_type::COLUMN_TYPE), attrs) if defined?(this_type::COLUMN_TYPE)) or
139
- raise HoboError, ("No input tag for #{this_field}:#{this_type} (this=#{this.inspect})")
140
- end
141
- if this_parent.errors[this_field]
142
- "<span class='field-with-errors'>#{the_input}</span>"
143
- else
144
- the_input
289
+ if this_parent.errors[this_field]
290
+ "<span class='field-with-errors'>#{the_input}</span>"
291
+ else
292
+ the_input
293
+ end
145
294
  end
146
295
  end
147
296
  %></def>
148
297
 
149
298
 
299
+ <!-- This tag is called by `<input>` when the context is a `has_many :through` collection. By default a `<select-many>`
300
+ is used, but this can be customised on a per-type basis. For example, say you would like the `<check-many>` tag used to
301
+ edit collections a `Category` model in your application:
302
+
303
+ <def tag="collection-input" for="Category"><check-many merge/></def>
304
+ -->
305
+ <def tag="collection-input" polymorphic></def>
306
+
307
+ <!-- The default `<collection-input>` - calls `<select-many>` -->
308
+ <def tag="collection-input" for="ActiveRecord::Base"><select-many merge/></def>
309
+
310
+
311
+ <!-- A `<textarea>` input -->
150
312
  <def tag="input" for="text" attrs="name">
151
313
  <%= text_area_tag(name, this, attributes) %>
152
314
  </def>
153
315
 
316
+ <!-- A checkbox plus a hidden-field. The hidden field trick comes from Rails - it means that when the checkbox is not checked, the parameter name is still submitted, with a '0' value (the value is '1' when the checkbox is checked) -->
154
317
  <def tag="input" for="boolean" attrs="name">
155
- <%= check_box_tag(name, '1', this, attributes) + hidden_field_tag(name, '0') %>
318
+ <%= check_box_tag(name, '1', this, attributes) %><%= hidden_field_tag(name, '0') unless attributes[:disabled] %>
156
319
  </def>
157
320
 
321
+ <!-- A password input - `<input type='password'>` -->
158
322
  <def tag="input" for="password" attrs="name">
159
323
  <%= password_field_tag(name, this, attributes) %>
160
324
  </def>
161
325
 
326
+ <!-- A date picker, using the `select_date` helper from Rails
327
+
328
+ ### Attributes
329
+
330
+ - order: The order of the year, month and day menus. A comma separated string or an array. Default: "year, month, day"
331
+
332
+ Any other attributes are passed through to the `select_date` helper.
333
+
334
+ The menus default to the current date if the current value is nil.
335
+
336
+ -->
162
337
  <def tag="input" for="date" attrs="order">
163
338
  <% order = order.nil? ? [:year, :month, :day] : comma_split(order).*.to_sym -%>
164
339
  <%= select_date(this || Time.now, attributes.merge(:prefix => param_name_for_this, :order => order)) %>
165
340
  </def>
166
341
 
342
+
343
+ <!-- A date/time picker, using the `select_date` helper from Rails
344
+
345
+ ### Attributes
346
+
347
+ - order: The order of the year, month and date menus. A comma separated string or an array. Default: "year, month,
348
+ day, hour, minute, second"
349
+
350
+ Any other attributes are passed through to the `select_date` helper.
351
+
352
+ The menus default to the current time if the current value is nil.
353
+
354
+ -->
167
355
  <def tag="input" for="time" attrs="order">
168
356
  <% order = order.nil? ? [:year, :month, :day, :hour, :minute, :second] : comma_split(order).*.to_sym -%>
169
357
  <%= select_date(this || Time.now, attributes.merge(:prefix => param_name_for_this, :order => order)) %>
170
358
  </def>
171
359
 
360
+
361
+ <!-- A date/time picker, using the `select_datetime` helper from Rails
362
+
363
+ ### Attributes
364
+
365
+ - order: The order of the year, month and date menus. A comma separated string or an array. Default: "year, month,
366
+ day, hour, minute, second"
367
+
368
+ Any other attributes are passed through to the `select_datetime` helper.
369
+
370
+ The menus default to the current time if the current value is nil.
371
+
372
+ -->
172
373
  <def tag="input" for="datetime" attrs="order">
173
374
  <% order = order.nil? ? [:year, :month, :day, :hour, :minute, :second] : comma_split(order).*.to_sym -%>
174
375
  <%= select_datetime(this || Time.now, attributes.merge(:prefix => param_name_for_this, :order => order)) %>
175
376
  </def>
176
377
 
378
+ <!-- An `<input type='text'>` input. -->
177
379
  <def tag="input" for="integer" attrs="name">
178
380
  <%= text_field_tag(name, this, attributes) %>
179
381
  </def>
180
382
 
383
+ <!-- An `<input type='text'>` input. -->
181
384
  <def tag="input" for="float" attrs="name">
182
385
  <%= text_field_tag(name, this, attributes) %>
183
386
  </def>
184
387
 
388
+ <!-- An `<input type='text'>` input. -->
185
389
  <def tag="input" for="string" attrs="name">
186
390
  <%= text_field_tag(name, this, attributes) %>
187
391
  </def>
188
392
 
393
+ <!-- An `<input type='text'>` input. -->
189
394
  <def tag="input" for="big_integer" attrs="name">
190
395
  <%= text_field_tag(name, this, attributes) %>
191
396
  </def>
192
397
 
398
+ <!-- A `<select>` menu containing the values of an 'enum string'.
399
+
400
+ ### Attributes
401
+
402
+ - labels: A hash that gives custom labels for the values of the enum.
403
+ Any values that do not have corresponding keys in this hash will get `value.titleize` as the label.
404
+
405
+ - titleize: Set to false to have the value itself (rather than `value.titleize`) be the default label. Default: true
406
+
407
+ -->
193
408
  <def tag="input" for="HoboFields::EnumString" attrs="labels, titleize"><%
194
409
  labels ||= {}
195
410
  titleize = true if titleize.nil?
@@ -201,12 +416,29 @@
201
416
  </def>
202
417
 
203
418
 
204
- <!-- Buttons -->
419
+ <!-- Provides either an ajax or non-ajax button to invoke a "remote method" or "web method" declared in the controller.
420
+ Web Methods provide support for the RPC model of client-server interaction, in contrast to the REST model. The
421
+ preference in Rails is to use REST as much as possible, but we are pragmatists, and sometimes you just to need a remote
422
+ procedure call.
423
+
424
+ The URL that the call is POSTed to is the `object_url` of `this`, plus the method name
425
+
426
+ `<remote-method-button>` supports all of the standard ajax attributes (see the main taglib documention for Rapid
427
+ Forms). If any ajax attributes are given, the button becomes an ajax button, if not,
205
428
 
429
+ ### Attributes
430
+
431
+ - method: the name of the web-method to call
432
+
433
+ - label: the label on the button
434
+
435
+ -->
206
436
  <def tag="remote-method-button" attrs="method, update, label, confirm"><%=
207
437
  ajax_attributes, html_attributes = attributes.partition_hash(Hobo::RapidHelper::AJAX_ATTRS)
208
438
 
209
- url = object_url(this, method, :method => :post)
439
+ url = object_url(this, method.to_s.gsub('-', '_'), :method => :post)
440
+ raise ArgumentError, "no such web method '#{method}' on #{this.typed_id}" unless url
441
+
210
442
  add_classes!(html_attributes, "button remote-method-button #{method}-button")
211
443
  label ||= method.titleize
212
444
  if update || !ajax_attributes.empty?
@@ -218,8 +450,26 @@
218
450
  button_to(label, url, html_attributes.merge(:confirm => confirm))
219
451
  end
220
452
  %></def>
453
+
454
+
455
+ <!-- Provides an ajax button to send a RESTful update or "PUT" to the server. i.e. to udate one or more fields of a
456
+ record.
221
457
 
458
+ Note that unlike simliar tags, `<update-button>` does not support both ajax and non-ajax modes at this time. It only
459
+ does ajax.
222
460
 
461
+ `<update-button>` supports all of the standard ajax attributes (see the main taglib documention for Rapid Forms).
462
+
463
+ ### Attributes
464
+
465
+ - label: The label on the button.
466
+
467
+ - fields: A hash with new field values pairs to update the resource with. The items in the hash will be converted to
468
+ HTTP parameters.
469
+
470
+ - params: Another hash with additional HTTP parameters to include in the ajax request
471
+
472
+ -->
223
473
  <def tag="update-button" attrs="label, update, fields, params"><%=
224
474
  raise HoboError.new("no update specified") unless update
225
475
 
@@ -233,8 +483,39 @@
233
483
  </def>
234
484
 
235
485
 
486
+ <!-- Provides either an ajax or non-ajax delete button to send a RESTful "DELETE". The context should be a record for
487
+ which you to want provide a delete button.
488
+
489
+ The Rapid Library has a convention of marking (in the output HTML, using a special CSS class) elements as "object
490
+ elements", with the class and ID of the ActiveRecord object that they represent. `<delete-button>` assumes it is placed
491
+ inside such an element, and will automatically find the right element to remove (fade out) from the DOM. The
492
+ `<collection>` tag adds this metadata (CSS class) automatically, so `<delete-button>` works well when used inside a
493
+ `<collection>`. This is a Clever Trick which needs to be revisted and perhaps simplified.
494
+
495
+ If used within a `<collection>`, `<delete-button>` also knows how to add an "empty message" such as "no comments to
496
+ display" when you delete the last item. Clever Tricks abound.
497
+
498
+ Current limitation: There is no support for the ajax callbacks at this time.
499
+
500
+ All the standard ajax attributes *except the callbacks* are supported (see the main taglib documention for Rapid Forms).
501
+
502
+
503
+
504
+ ### Attributes
505
+
506
+ - label: The label for the button. Default: "Remove"
507
+
508
+ - in-place: delete in place (ajax)? Default: true, or false if the record to be deleted is the same as the top level
509
+ context of the page
510
+
511
+ - image: URL of an image for the button. Changes the rendered tag from `<input type='button'>` to `<input type='image'
512
+ src='...'>`
513
+
514
+ - fade: Perform the fade effect (true/false)? Default: true
515
+
516
+ -->
236
517
  <def tag="delete-button" attrs="label, update, in-place, image, confirm, fade, subsite"><%=
237
- in_place = false if in_place.nil? && this == @this && !request.xhr?
518
+ in_place = false if in_place.nil? && this == @this && request.method == :get
238
519
  url = object_url(this, :method => :delete, :subsite => subsite)
239
520
  if (Hobo::Dryml.last_if = url && can_delete?)
240
521
  attributes = attributes.merge(if image
@@ -266,6 +547,15 @@
266
547
  %></def>
267
548
 
268
549
 
550
+ <!-- Provides either an ajax or non-ajax create button that will send a RESTful "POST" to the server to create a new resource.
551
+
552
+ All of the standard ajax attributes are supported (see the main taglib documention for Rapid Forms).
553
+
554
+ ### Attributes
555
+
556
+ - model: The class to instantiate, pass either the class name or the class object.
557
+
558
+ -->
269
559
  <def tag="create-button" attrs="model, update, label, fields, message"><%=
270
560
  raise HoboError.new("no update specified") unless update
271
561
 
@@ -293,19 +583,20 @@
293
583
 
294
584
 
295
585
  <def tag="select-one" attrs="include-none, blank-message, options, sort"><%
296
- raise HoboError.new("Not allowed to edit") unless can_edit?
297
-
586
+ raise HoboError.new("Not allowed to edit #{this_field}") if !attributes[:disabled] && !can_edit?
587
+
298
588
  blank_message ||= "(No #{this_type.name.to_s.titleize})"
299
- conditions = ActiveRecord::Associations::BelongsToAssociation.new(this_parent, this_field_reflection).conditions
300
- options ||= this_field_reflection.klass.all(:conditions => conditions).select {|x| can_view?(x)}
301
- #Todo: switch to autocompleter for id_name when too many records, and id_name supported
302
- select_options = options.map { |x|
303
- [ name(:with => x, :raw => true), x.id ]
304
- }
589
+
590
+ options ||= begin
591
+ conditions = ActiveRecord::Associations::BelongsToAssociation.new(this_parent, this_field_reflection).conditions
592
+ this_field_reflection.klass.all(:conditions => conditions).select {|x| can_view?(x)}
593
+ end
594
+
595
+ select_options = options.map { |x| [x.to_s, x.id ] }
305
596
  select_options = select_options.sort if sort
306
597
  select_options.insert(0, [blank_message, ""]) if include_none || (this.nil? && include_none != false)
307
598
  attributes = add_classes(attributes, "input", "belongs_to", type_and_field)
308
- %>
599
+ -%>
309
600
  <select name="#{param_name_for_this(true)}" merge-attrs="&attributes.except :name">
310
601
  <%= options_for_select(select_options, this ? this.id : "") %>
311
602
  </select>
@@ -315,9 +606,9 @@
315
606
  <def tag="name-one" attrs="complete-target, completer"><%
316
607
  complete_target ||= this_field_reflection.klass
317
608
  completer ||= (complete_target.is_a?(Class) ? complete_target : complete_target.class).name_attribute
318
- %>
609
+ -%>
319
610
  <input type="text" name="#{param_name_for_this}"
320
- class="autocompleter #{type_and_field} complete-on:#{dom_id complete_target}:#{completer}"
611
+ class="autocompleter #{type_and_field.dasherize} #{css_data :complete_on, typed_id(complete_target), completer}"
321
612
  value="&name :no_wrapper => true, :if_present => true"
322
613
  merge-attrs/>
323
614
  <div class="completions-popup" style="display:none"></div>
@@ -325,7 +616,7 @@
325
616
 
326
617
 
327
618
  <def tag="sti-type-input">
328
- <select name="#{param_name_for(form_this, form_field_path + ['type'])}">
619
+ <select name="#{param_name_for(form_field_path + ['type'])}">
329
620
  <%= options_for_select(this.class.send(:subclasses).map{|x| [x.name.titleize, x.name]}, this.class.name) %>
330
621
  </select>
331
622
  </def>
@@ -340,24 +631,25 @@
340
631
  <section class="error-messages" merge-attrs if="&this.errors.length > 0">
341
632
  <h2 param="heading">To proceed please correct the following:</h2>
342
633
  <ul param>
343
- <% this.errors.each do |attr, message|; next if message == "..." %>
634
+ <% this.errors.each do |attr, message|; next if message == "..." -%>
344
635
  <li param><%= attr.titleize %> <%= message %></li>
345
- <% end %>
636
+ <% end -%>
346
637
  </ul>
347
638
  </section>
348
639
  </def>
349
640
 
350
641
 
351
- <def tag="select-many" attrs="options, targets, remove-label, prompt">
352
- <%
353
- prompt ||= "Add #{a_or_an(:word => this_field.titleize.singularize)}"
354
- options ||= begin
355
- conditions = ActiveRecord::Associations::HasManyThroughAssociation.new(this_parent, this_field_reflection).sql_conditions
356
- this_field_reflection.klass.all(:conditions => conditions).select {|x| can_view?(x)}
357
- end
642
+ <!--
643
+ An input for `has_many :through` associations that lets the user chose the items from a `<select>` menu.
644
+
645
+ To use this tag, the model of the items the user is chosing *must* have unique names, and the
646
+ -->
647
+ <def tag="select-many" attrs="options, targets, remove-label, prompt, disabled"><%
648
+ prompt ||= "Add #{this_field.titleize.singularize}"
649
+ options ||= this_field_reflection.klass.all(:conditions =>this.conditions).select {|x| can_view?(x)}
358
650
 
359
651
  values = this
360
- %>
652
+ -%>
361
653
  <div class="input select-many" merge-attrs>
362
654
  <div style="display:none" class="item-proto">
363
655
  <div class="item" param="proto-item">
@@ -368,26 +660,52 @@
368
660
  </div>
369
661
  <div class="items">
370
662
  <set param-name="&param_name_for_this"/>
371
- <repeat>
372
- <div class="item" param="item">
373
- <span><name/></span>
374
- <input type="hidden" name="#{param_name}[]" value="#{name :no_wrapper => true}" param="hidden"/>
375
- <input type="button" class="remove-item" value="#{remove_label || 'Remove'}" param="remove-button"/>
376
- </div>
377
- </repeat>
663
+ <div class="item" param="item" repeat>
664
+ <span><%= h this.to_s %></span>
665
+ <input type="hidden" name="#{param_name}[]" value="@#{h this.id}" disabled="&disabled"
666
+ param="hidden"/>
667
+ <input type="button" class="remove-item" value="#{remove_label || 'Remove'}" disabled="&disabled"
668
+ param="remove-button"/>
669
+ </div>
378
670
  </div>
379
- <select>
671
+ <select merge-attrs="&{:disabled => disabled}">
380
672
  <option value=""><prompt/></option>
381
- <option repeat="&options.sort_by {|x| name(:no_wrapper => true, :with => x).downcase}"
382
- merge-attrs="&{:disabled => 'true'} if this.in?(values)"><name no-wrapper/></option>
673
+ <option repeat="&options.sort_by {|x| x.to_s.downcase}" value="@#{this.id}"
674
+ merge-attrs="&{:disabled => 'true'} if this.in?(values)"><%= h this.to_s %></option>
383
675
  </select>
384
676
  </div>
385
677
  </def>
386
678
 
679
+ <!--
680
+ Used inside a form to specify where to redirect after successful submission. This works by inserting a hidden field called `after_submit` which is used by Hobo if present to perform a redirect after the form submission.
387
681
 
388
- <def tag="after-submit" attrs="uri, stay-here, go-back">
389
- <% uri = "stay-here" if stay_here %>
390
- <% uri = session[:previous_uri] if go_back %>
682
+ ### Usage
683
+
684
+ Use the `stay-here` attribute to remain on the current page:
685
+
686
+ <form>
687
+ <after-submit stay-here/>
688
+ ...
689
+ </form>
690
+
691
+ Use the `go-back` option to return to the previous page:
692
+
693
+ <form>
694
+ <after-submit go-back/>
695
+ ...
696
+ </form>
697
+
698
+ Use the `uri` option to specify a redirect location:
699
+
700
+ <form>
701
+ <after-submit uri="/admin"/>
702
+ ...
703
+ </form>
704
+ -->
705
+ <def tag="after-submit" attrs="uri, stay-here, go-back"><%
706
+ uri = "stay-here" if stay_here
707
+ uri = session[:previous_uri] if go_back
708
+ -%>
391
709
  <input type="hidden" value="&params[:after_submit] || uri" name="after_submit" if="&uri"/>
392
710
  </def>
393
711
 
@@ -400,6 +718,17 @@
400
718
  </def>
401
719
 
402
720
 
721
+ <def tag="check-many">
722
+ <% collection = this; param_name = param_name_for_this; options ||= this.member_class.find(:all) -%>
723
+ <ul class="check-many">
724
+ <li repeat="&options">
725
+ <input type="checkbox" name="#{param_name}[]" value="&h this.to_s" checked="&this.in?(collection)"/>
726
+ <name/>
727
+ </li>
728
+ </ul>
729
+ </def>
730
+
731
+
403
732
  <def tag="hidden-id-field">
404
733
  <if:id><input type="hidden" name="#{param_name_for_this}" value="#{this}" /></if>
405
734
  </def>
@@ -407,22 +736,22 @@
407
736
 
408
737
  <def tag="input-many">
409
738
  <set empty="&this.empty?"/>
410
- <ul class="input-many #{this_field.dasherize} input-many-prefix:#{param_name_for_this}">
739
+ <ul class="input-many #{this_field.dasherize} #{css_data :input_many_prefix, param_name_for_this}">
411
740
  <li repeat class="#{'record-with-errors' unless this.errors.empty?}">
412
741
  <error-messages without-heading class="sub-record"/>
413
742
  <hidden-id-field/>
414
743
  <div class="input-many-item" param="default"/>
415
744
  <div class="buttons">
416
- <button class="remove-item" unless="&this_parent.length == 1">-</button>
417
- <button class="add-item" if="&last_item?">+</button>
745
+ <button class="remove-item" unless="&this_parent.length == 1" merge-attrs="disabled">-</button>
746
+ <button class="add-item" if="&last_item?" merge-attrs="disabled">+</button>
418
747
  </div>
419
748
  </li>
420
749
  <li if="&empty">
421
- <fake-field-context fake-field="0" context="&this.member_class.new">
750
+ <fake-field-context fake-field="0" context="&this.new">
422
751
  <div class="input-many-item" param="default"/>
423
752
  </fake-field-context>
424
753
  <div class="buttons">
425
- <button class="add-item">+</button>
754
+ <button class="add-item" merge-attrs="disabled">+</button>
426
755
  </div>
427
756
  </li>
428
757
  </ul>
@@ -430,7 +759,7 @@
430
759
 
431
760
 
432
761
  <def tag="input-all">
433
- <% association_fkey = this_field_reflection.primary_key_name %>
762
+ <% association_fkey = this_field_reflection.primary_key_name -%>
434
763
  <ul class="input-all #{this_field.dasherize}">
435
764
  <li repeat class="#{'record-with-errors' unless this.errors.empty?}">
436
765
  <set-scoped form-field-names="&[]">
@@ -443,19 +772,9 @@
443
772
  </def>
444
773
 
445
774
 
446
-
447
-
448
-
449
-
450
-
451
-
452
-
453
-
454
-
455
-
456
-
457
-
458
-
459
-
460
-
461
-
775
+ <def tag="or-cancel">
776
+ <if test="&linkable?">or <a>Cancel</a></if>
777
+ <else>
778
+ <if test="&linkable?(this.class)">or <a to="&this.class">Cancel</a></if>
779
+ </else>
780
+ </def>