hobo 1.3.3 → 1.4.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. data/{CHANGES.txt → CHANGES-1.3.txt} +0 -0
  2. data/CHANGES-1.4.txt +678 -0
  3. data/Rakefile +13 -3
  4. data/TODO-1.4.txt +69 -0
  5. data/VERSION +1 -1
  6. data/app/helpers/hobo_debug_helper.rb +16 -0
  7. data/app/helpers/hobo_deprecated_helper.rb +45 -0
  8. data/app/helpers/hobo_helper_base.rb +8 -0
  9. data/app/helpers/hobo_permissions_helper.rb +136 -0
  10. data/app/helpers/hobo_route_helper.rb +196 -0
  11. data/{lib/hobo/helper/translations.rb → app/helpers/hobo_translations_helper.rb} +4 -7
  12. data/{lib/hobo/helper/translations/normalizer.rb → app/helpers/hobo_translations_normalizer_helper.rb} +3 -10
  13. data/app/helpers/hobo_type_helper.rb +24 -0
  14. data/app/helpers/hobo_view_hint_helper.rb +13 -0
  15. data/hobo.gemspec +3 -3
  16. data/lib/generators/hobo/admin_subsite/admin_subsite_generator.rb +0 -9
  17. data/lib/generators/hobo/admin_subsite/templates/application.dryml +2 -0
  18. data/lib/generators/hobo/admin_subsite/templates/gitkeep +0 -0
  19. data/lib/generators/hobo/admin_subsite/templates/site.css.erb +9 -0
  20. data/lib/generators/hobo/admin_subsite/templates/site.js.erb +10 -0
  21. data/lib/generators/hobo/assets/assets_generator.rb +16 -2
  22. data/lib/generators/hobo/assets/templates/application.css +9 -0
  23. data/lib/generators/hobo/assets/templates/application.dryml.erb +0 -5
  24. data/lib/generators/hobo/assets/templates/application.js +11 -0
  25. data/lib/generators/hobo/assets/templates/front.css +10 -0
  26. data/lib/generators/hobo/assets/templates/front.js +11 -0
  27. data/lib/generators/hobo/assets/templates/front_site.dryml.erb +6 -0
  28. data/lib/generators/hobo/assets/templates/gitkeep +0 -0
  29. data/lib/generators/hobo/dev_tweaks/dev_tweaks_generator.rb +31 -0
  30. data/lib/generators/hobo/i18n/templates/hobo.en.yml +1 -1
  31. data/lib/generators/hobo/install_plugin/USAGE +3 -0
  32. data/lib/generators/hobo/install_plugin/install_plugin_generator.rb +36 -0
  33. data/lib/generators/hobo/plugin.rb +112 -0
  34. data/lib/generators/hobo/setup_wizard/setup_wizard_generator.rb +31 -14
  35. data/lib/generators/hobo/subsite.rb +16 -2
  36. data/lib/generators/hobo/subsite/templates/gitkeep +0 -0
  37. data/lib/generators/hobo/subsite/templates/site.css.erb +9 -0
  38. data/lib/generators/hobo/subsite/templates/site.js.erb +10 -0
  39. data/lib/generators/hobo/subsite_taglib/templates/taglib.dryml.erb +0 -17
  40. data/lib/generators/hobo/test_framework/test_framework_generator.rb +1 -1
  41. data/lib/hobo.rb +3 -2
  42. data/lib/hobo/controller.rb +43 -24
  43. data/lib/hobo/controller/model.rb +63 -42
  44. data/lib/hobo/controller/user_base.rb +1 -3
  45. data/lib/hobo/engine.rb +1 -1
  46. data/lib/hobo/extensions/active_record/associations/association.rb +36 -0
  47. data/lib/hobo/extensions/active_record/associations/collection.rb +10 -19
  48. data/lib/hobo/extensions/active_record/associations/proxy.rb +3 -15
  49. data/lib/hobo/extensions/active_record/associations/scope.rb +2 -2
  50. data/lib/hobo/extensions/active_record/permissions.rb +32 -38
  51. data/lib/hobo/extensions/active_record/relation_with_origin.rb +5 -5
  52. data/lib/hobo/model.rb +12 -7
  53. data/lib/hobo/model/accessible_associations.rb +8 -15
  54. data/lib/hobo/model/lifecycles/creator.rb +1 -1
  55. data/lib/hobo/model/lifecycles/transition.rb +1 -1
  56. data/lib/hobo/model/permissions.rb +4 -4
  57. data/lib/hobo/model/scopes.rb +4 -17
  58. data/lib/hobo/model/scopes/automatic_scopes.rb +5 -13
  59. data/lib/hobo/rapid/helper.rb +1 -161
  60. data/lib/hobo/rapid/taglibs/rapid.dryml +3 -17
  61. data/test/doctest/hobo/hobo_helper.rdoctest +8 -44
  62. data/{doctests → test/doctest}/hobo/lifecycles.rdoctest +0 -0
  63. data/{doctests → test/doctest}/hobo/model.rdoctest +2 -4
  64. data/{doctests → test/doctest}/hobo/multi_model_forms.rdoctest +3 -24
  65. data/{doctests → test/doctest}/hobo/scopes.rdoctest +3 -53
  66. data/test/doctest/prepare_testapp.rb +11 -0
  67. data/test/irt/generators/admin_subsite.irt +1 -19
  68. data/test/irt/generators/assets.irt +4 -9
  69. data/test/irt/generators/controller.irt +0 -3
  70. data/test/irt/generators/front_controller.irt +0 -5
  71. data/test/irt/generators/{helper.rb → irt_helper.rb} +2 -2
  72. data/test/irt/generators/model.irt +1 -12
  73. data/test/irt/generators/partials/_account_user_model_tests.rb +0 -8
  74. data/test/irt/generators/partials/_accounts_users_controller_tests.rb +0 -2
  75. data/test/irt/generators/partials/_default_user_model_tests.rb +0 -8
  76. data/test/irt/generators/partials/_default_users_controller_tests.rb +0 -2
  77. data/test/irt/generators/partials/_house_controller_tests.rb +0 -2
  78. data/test/irt/generators/partials/_house_model_tests.rb +1 -9
  79. data/test/irt/generators/partials/_subsite_taglib_admin.rb +5 -2
  80. data/test/irt/generators/partials/_subsite_taglib_admin_invite_only.rb +1 -1
  81. data/test/irt/generators/partials/_subsite_taglib_noopt.rb +2 -2
  82. data/test/irt/generators/partials/_subsite_taglib_variables.rb +0 -15
  83. data/test/irt/generators/partials/_user_mailer_tests.rb +1 -3
  84. data/test/irt/generators/resource.irt +0 -3
  85. data/test/irt/generators/subsite.irt +6 -22
  86. data/test/irt/generators/subsite_taglib.irt +0 -18
  87. data/test/irt/generators/test_framework.irt +2 -5
  88. data/test/irt/generators/user_controller.irt +0 -3
  89. data/test/irt/generators/user_mailer.irt +0 -3
  90. data/test/irt/generators/user_model.irt +0 -3
  91. data/test/irt/generators/user_resource.irt +0 -3
  92. data/test/irt/readme.txt +6 -3
  93. metadata +116 -159
  94. data/app/controllers/dev_controller.rb +0 -25
  95. data/app/views/dev/summary.dryml +0 -102
  96. data/doctests/prepare_testapp.rb +0 -8
  97. data/lib/generators/hobo/admin_subsite/templates/admin.css +0 -20
  98. data/lib/generators/hobo/rapid/USAGE +0 -3
  99. data/lib/generators/hobo/rapid/rapid_generator.rb +0 -24
  100. data/lib/generators/hobo/rapid/templates/IE7.js +0 -2
  101. data/lib/generators/hobo/rapid/templates/blank.gif +0 -0
  102. data/lib/generators/hobo/rapid/templates/hobo-rapid.css +0 -94
  103. data/lib/generators/hobo/rapid/templates/hobo-rapid.js +0 -1015
  104. data/lib/generators/hobo/rapid/templates/ie7-recalc.js +0 -166
  105. data/lib/generators/hobo/rapid/templates/lowpro.js +0 -339
  106. data/lib/generators/hobo/rapid/templates/reset.css +0 -95
  107. data/lib/generators/hobo/rapid/templates/themes/clean/public/images/101-3B5F87-ACD3E6.png +0 -0
  108. data/lib/generators/hobo/rapid/templates/themes/clean/public/images/30-3E547A-242E42.png +0 -0
  109. data/lib/generators/hobo/rapid/templates/themes/clean/public/images/30-DBE1E5-FCFEF5.png +0 -0
  110. data/lib/generators/hobo/rapid/templates/themes/clean/public/images/300-ACD3E6-fff.png +0 -0
  111. data/lib/generators/hobo/rapid/templates/themes/clean/public/images/50-ACD3E6-fff.png +0 -0
  112. data/lib/generators/hobo/rapid/templates/themes/clean/public/images/fieldbg.gif +0 -0
  113. data/lib/generators/hobo/rapid/templates/themes/clean/public/images/pencil.png +0 -0
  114. data/lib/generators/hobo/rapid/templates/themes/clean/public/images/small_close.png +0 -0
  115. data/lib/generators/hobo/rapid/templates/themes/clean/public/images/spinner.gif +0 -0
  116. data/lib/generators/hobo/rapid/templates/themes/clean/public/stylesheets/clean.css +0 -327
  117. data/lib/generators/hobo/rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +0 -102
  118. data/lib/generators/hobo/rapid/templates/themes/clean/views/clean.dryml +0 -10
  119. data/lib/hobo/helper.rb +0 -460
  120. data/lib/hobo/rapid/taglibs/rapid_core.dryml +0 -808
  121. data/lib/hobo/rapid/taglibs/rapid_document_tags.dryml +0 -56
  122. data/lib/hobo/rapid/taglibs/rapid_editing.dryml +0 -287
  123. data/lib/hobo/rapid/taglibs/rapid_forms.dryml +0 -1156
  124. data/lib/hobo/rapid/taglibs/rapid_generics.dryml +0 -48
  125. data/lib/hobo/rapid/taglibs/rapid_i18n.dryml +0 -173
  126. data/lib/hobo/rapid/taglibs/rapid_lifecycles.dryml +0 -96
  127. data/lib/hobo/rapid/taglibs/rapid_navigation.dryml +0 -108
  128. data/lib/hobo/rapid/taglibs/rapid_pages.dryml +0 -259
  129. data/lib/hobo/rapid/taglibs/rapid_plus.dryml +0 -247
  130. data/lib/hobo/rapid/taglibs/rapid_summary.dryml +0 -283
  131. data/lib/hobo/rapid/taglibs/rapid_support.dryml +0 -102
  132. data/lib/hobo/rapid/taglibs/rapid_user_pages.dryml +0 -182
  133. data/test/irt/generators/rapid.irt +0 -29
@@ -1,56 +0,0 @@
1
- <!-- Extra tags for semantic markup -->
2
-
3
- <!-- Used as a semantic wrapper around a group of sections and asides. CSS layouts can be provided based on this structure.
4
-
5
- ### Usage
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
- -->
13
- <def tag="section-group"><div class="section-group"><div class="section-group-inner" merge-attrs param="default"></div></div></def>
14
-
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
- -->
20
- <def tag="section" attrs="empty, with-flash-messages">
21
- <set body="&parameters.default" flash="&with_flash_messages && !scope.flash_rendered"/>
22
- <div class="section #{'with-flash' if flash}" merge-attrs if="&!body.blank? || empty || flash">
23
- <flash-messages if="&flash"/>
24
- <%= body %>
25
- </div>
26
- </def>
27
-
28
- <!-- A proposed HTML 5 semantic tag. Outputs `<div class="aside">` and works in the same way as `<section>` with empty content. -->
29
- <def tag="aside" attrs="empty">
30
- <set body="&parameters.default"/>
31
- <div class="aside" merge-attrs if="&!body.blank? || empty"><%= body %></div>
32
- </def>
33
-
34
- <!-- A proposed HTML 5 semantic tag. Outputs `<div class="header">` and works in the same way as `<section>` with empty content. -->
35
- <def tag="header" attrs="empty">
36
- <set body="&parameters.default"/>
37
- <div class="header" merge-attrs if="&!body.blank? || empty"><%= body %></div>
38
- </def>
39
-
40
- <!-- A proposed HTML 5 semantic tag. Outputs `<div class="footer">` and works in the same way as `<section>` with empty content. -->
41
- <def tag="footer" attrs="empty">
42
- <set body="&parameters.default"/>
43
- <div class="footer" merge-attrs if="&!body.blank? || empty"><%= body %></div>
44
- </def>
45
-
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,287 +0,0 @@
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
- -->
9
- <def tag="editor" ><%=
10
- if !can_edit?
11
- view(attributes)
12
- else
13
- attrs = add_classes(attributes, type_id, type_and_field, "editor")
14
- if (refl = this_field_reflection)
15
- if refl.macro == :belongs_to
16
- belongs_to_editor(attrs)
17
- else
18
- has_many_editor(attrs)
19
- end
20
- else
21
- call_polymorphic_tag("editor", attrs) or
22
- raise Hobo::Error.new("<editor> not implemented for #{this.class.name}\##{this_field} " +
23
- "(#{this.inspect}:#{this_type})")
24
- end
25
- end
26
- %></def>
27
-
28
-
29
- <!-- Not implemented - you just get links to the items in the collection -->
30
- <def tag="has-many-editor">
31
- <% #TODO: Implement
32
- %>
33
- <a merge-attrs/>
34
- </def>
35
-
36
- <!-- Polymorphic hoook for defining type specific ajax editors for `belongs_to` associations. The default is `<select-one-editor>` -->
37
- <def tag="belongs-to-editor" polymorphic><%= select_one_editor(attributes) %></def>
38
-
39
- <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
40
- <def tag="editor" for="string"><%= in_place_editor(attributes, this) %></def>
41
-
42
- <!-- Provides a simple Scriptaculous in-place-editor that uses a `<textarea>` -->
43
- <def tag="editor" for="text"><%= in_place_editor(attributes, this) %></def>
44
-
45
- <!-- Provides a simple Scriptaculous in-place-editor that uses a `<textarea>`.
46
- A JavaScript hook is available in order to replace the simple textarea with a rich-text editor.
47
- For an example, see the [hoboyui](http://github.com/tablatom/hoboyui) plugin
48
-
49
- This tag does not sanitize HTML; this is provided by HtmlString before saving to the database.
50
- -->
51
- <def tag="editor" for="html"><%= in_place_editor(attributes, this) %></def>
52
-
53
- <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
54
- <def tag="editor" for="datetime"><%= in_place_editor(attributes, this) %></def>
55
-
56
- <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
57
- <def tag="editor" for="date"><%= in_place_editor(attributes, this) %></def>
58
-
59
- <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='integer'>` -->
60
- <def tag="editor" for="integer"><%= in_place_editor(attributes, this) %></def>
61
-
62
- <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='BigDecimal'>` -->
63
- <def tag="editor" for="BigDecimal"><%= in_place_editor(attributes, this) %></def>
64
-
65
- <!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
66
- <def tag="editor" for="float"><%= in_place_editor(attributes, this) %></def>
67
-
68
- <!-- Raises an error - passwords cannot be edited in place -->
69
- <def tag="editor" for="password"><% raise Hobo::Error, "passwords cannot be edited in place" %></def>
70
-
71
- <!-- calls `<boolean-checkbox-editor>` -->
72
- <def tag="editor" for="boolean"><boolean-checkbox-editor merge-attrs/></def>
73
-
74
- <!-- Provides an editor that uses a `<select>` menu. Uses the `<string-select-editor>` tag. -->
75
- <def tag="editor" for="HoboFields::Types::EnumString">
76
- <string-select-editor values="&this_type.values" merge/>
77
- </def>
78
-
79
- <!-- Provides a `<select>` menu with an ajax callback to update a `belongs_to` relationship when changed.
80
- By default the menu contains every record in the target model's table.
81
-
82
- ### Attributes
83
-
84
- - `include-none` - whether to include a 'none' option (i.e. set the foreign key to null). If this value is not supplied, the default is "true" if the current value is nil; otherwise the default is "false". One implication of this is that the default may change when the form is re-rendered due to a validation failure. Setting this value explicitly is recommended.
85
- - `blank-message` - the message for the 'none' option. Defaults to "(No `<model-name>`)", e.g. "(No Product)"
86
- - `options` - an array of records to include in the menu. Defaults to the all the records in the target table that match any `:conditions` declared on the `belongs_to` (subject to `limit`)
87
- - `sort` - whether to sort the array of options. Defaults to no sorting.
88
- - `limit` - if `options` is not specified, this limits the number of records. Default: 100
89
- - `text_method` - The method to call on each record to get the text for the option. Multiple methods are supported ie "institution.name"
90
- - `update` - one or more DOM ID's (comma separated string or an array) to be updated as part of the ajax call.
91
-
92
- 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.
93
-
94
- -->
95
- <def tag="select-one-editor" attrs="include-none, blank-message, options, sort, limit, text-method, update"><%
96
- raise Hobo::Error.new("Not allowed to edit") unless can_edit?
97
- blank_message ||= ht("#{this_type.name.to_s.underscore}.select_one_editor.blank_message", :count => 0, :default => "(No #{this_type.name.to_s.titleize})")
98
- limit ||= 100
99
-
100
- if options.blank? || !options.first.respond_to?(:first)
101
- options ||= begin
102
- conditions = ActiveRecord::Associations::BelongsToAssociation.new(this_parent, this_field_reflection).send(:conditions)
103
- order = this_field_reflection.klass.default_order
104
- this_field_reflection.klass.all(:conditions => conditions, :limit => limit, :order => order).select {|x| can_view?(x)}
105
- end
106
-
107
- id_method = this_field_reflection.options[:primary_key] || this_field_reflection.klass.primary_key
108
- if text_method.nil?
109
- select_options = options.map { |x| [name(:with => x, :no_wrapper => true), x.send(id_method)] }
110
- else
111
- select_options = options.map do |x|
112
- [ text_method.split(".").inject(x) { |v, method| v.send(method) },
113
- x.send(id_method) ]
114
- end
115
- end
116
- else
117
- # handle the old style, where options could be passed an options_for_select style array
118
- select_options = options
119
- end
120
- select_options = select_options.sort if sort
121
- select_options.insert(0, [blank_message, ""]) if include_none || (this.nil? && include_none != false)
122
-
123
- f = ajax_updater(object_url(this_parent, :method => :put),
124
- update,
125
- :method => "put",
126
- :params => { this_parent.class.name.underscore => {
127
- this_field_reflection.primary_key_name => Hobo.raw_js('this.value')
128
- } })
129
- %>
130
- <select onchange="#{f}" merge-attrs>
131
- <%= options_for_select(select_options, this ? this.send(id_method) : "") %>
132
- </select>
133
- </def>
134
-
135
- <!-- Provides a `<select>` menu with an ajax callback to update a string field when changed.
136
-
137
- ### Attributes
138
-
139
- - values: The values for the menu options. Required
140
-
141
- - Labels: A hash that can be used to customise the labels for the menu.
142
- Any value that does not have a corresponding key in this hash will have its label
143
- generated by `value.titleize`
144
-
145
- - titleize: Set to false to have the default labels be the same as the values. Default: true - the labels are generated by `value.titleize`
146
-
147
- - update: one or more DOM ID's (comma separated string or an array) to be updated as part of the ajax call.
148
-
149
- 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.
150
-
151
- -->
152
- <def tag="string-select-editor" attrs="update, values, labels, titleize"><%
153
- raise Hobo::Error.new("Not allowed to edit") unless can_edit?
154
-
155
- values = comma_split(values)
156
- labels ||= {}
157
- titleize = true if titleize.nil?
158
- options = values.map {|v| [labels.fetch(v.to_sym, titleize ? v.titleize : v), v] }
159
-
160
- f = ajax_updater(object_url(this_parent, :method => :put),
161
- update,
162
- :method => "put",
163
- :params => { this_parent.class.name.underscore => {
164
- this_field => Hobo.raw_js('this.value')
165
- } })
166
- %>
167
- <select onchange="#{f}" merge-attrs>
168
- <%= options_for_select(options, this) %>
169
- </select>
170
- </def>
171
-
172
- <!-- A checkbox with an ajax callback to update a boolean field when clicked.
173
-
174
- ### Attributes
175
-
176
- - update: one or more DOM ID's (comma separated string or an array) to be updated as part of the ajax call.
177
-
178
- 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.
179
-
180
- - message: A message to display in the ajax-progress spinner. Default: "Saving..."
181
-
182
- -->
183
- <def tag="boolean-checkbox-editor" attrs="update, message"><%
184
- raise Hobo::Error.new("Not allowed to edit") unless can_edit?
185
- f = ajax_updater(object_url(this_parent, :method => :put),
186
- update,
187
- :method => "put",
188
- :message => message,
189
- :spinner_next_to => Hobo.raw_js("this"),
190
- :params => { this_parent.class.name.underscore => {
191
- this_field => Hobo.raw_js('this.checked')
192
- } })
193
- %>
194
- <input type="checkbox" value="1" onclick="#{f}"
195
- merge-attrs="& this ? attributes.merge(:checked => 'checked') : attributes" />
196
- </def>
197
-
198
-
199
- <!-- nodoc. -->
200
- <def tag="sti-type-editor" attrs="update">
201
- <% base_class = this.class
202
- base_class = base_class.superclass while base_class.superclass != ActiveRecord::Base
203
- f = ajax_updater("#{base_url}/#{controller_for base_class}/#{this.id}",
204
- update,
205
- :method => "put",
206
- :params => { base_class.name.underscore => {
207
- "type" => Hobo.raw_js('this.value')
208
- } })
209
- %>
210
-
211
- <select onchange="#{f}">
212
- <%= parameters.default || options_for_select(base_class.send(:descendants).map{|x| [x.name.titleize, x.name]},
213
- this.class.name) %>
214
- </select>
215
- </def>
216
-
217
-
218
- <!-- Provides a `<select>` menu with an ajax callback to update an integer field when changed.
219
-
220
- ### Attributes
221
-
222
- - min: The minimum end of the range of numbers to include
223
-
224
- - max: A male name, short for Maximilian
225
-
226
- - options: An array of numbers to use if min..max is not enough for your needs.
227
-
228
- - nil-option: Label to give if the current value is nil. Default: "Choose a value"
229
-
230
- - message: A message to display in the ajax-progress spinner. Default: "Saving..."
231
-
232
- - update: one or more DOM ID's (comma separated string or an array) to be updated as part of the ajax call.
233
-
234
- 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.
235
-
236
- -->
237
- <def tag="integer-select-editor" attrs="options, min, max, update, nil-option, message">
238
- <% options ||= (min.to_i..max.to_i).to_a %>
239
- <select class="integer editor #{update_elements_class(update)} #{model_id_class(this_parent, this_field)}"
240
- merge-attrs="&message ? attributes.merge(:hobo_message => message) : attributes">
241
- <if test="&this.nil?"><option value=""><%= nil_option || "Choose a value" %></option></if>
242
- <%= options_for_select(options.*.to_s, this.to_s) %>
243
- </select>
244
- </def>
245
-
246
-
247
- <!-- nodoc. -->
248
- <def tag="has-many-checkbox-editor" attrs="model, update, message"><%=
249
- raise Hobo::Error.new("no update specified") unless update
250
-
251
- fields = attributes.delete_if{|k,v|!k.ends_with? "_id"}
252
- conditions = fields.map{|k,v|"#{k}=#{v}"}.join " AND "
253
-
254
- klass = model.is_a?(String) ? model.constantize : model
255
- obj = klass.where(conditions).first
256
-
257
- checkbox_attrs = {:type =>'checkbox'}
258
-
259
- if obj == nil
260
- new = klass.new(fields)
261
- permission = if can_create?(new)
262
- class_name = new.class.name.underscore
263
- ajax_options = { :message => message }
264
- ajax_options[:params] = { class_name => fields } unless fields.empty?
265
- checkbox_attrs[:onclick] = ajax_updater(object_url(new.class, :method => :post), update, ajax_options)
266
- end
267
- else
268
- permission = if can_delete?(obj)
269
- checkbox_attrs[:checked] = 'checked'
270
- message ||= "Unsetting #{obj.class.name.titleize}"
271
- class_name = obj.class.name.underscore
272
- checkbox_attrs[:onclick] = ajax_updater(object_url(obj, :method => :delete), update, {:message => message, :method => 'delete'})
273
- end
274
- end
275
- element(:input, add_classes(attributes.merge(checkbox_attrs),
276
- "checkbox_input has_many_checkbox has_many_#{class_name}_checkbox")) if permission
277
- %></def>
278
-
279
- <!-- nodoc. -->
280
- <def tag="has-many-checkbox-editors">
281
- <table>
282
- <tr:>
283
- <td><has-many-checkbox-editor param="editor" merge-attrs/></td>
284
- <td><name param/></td>
285
- </tr>
286
- </table>
287
- </def>
@@ -1,1156 +0,0 @@
1
- <!-- Rapid Forms provides various tags that make it quick and easy to produce working new or edit forms.
2
-
3
- ### Overview
4
-
5
- The main tags are:
6
-
7
- - `<form>`, which acts like the dumb HTML tag if you provide the `action` attribute, and picks up various Rapid smarts
8
- otherwise.
9
-
10
- - `<input>`, which automatically choses an appropriate form control based on the type of the date.
11
-
12
- ### Ajax Attributes
13
-
14
- Several of the tags in this taglib support the following set of ajax attributes:
15
-
16
- - update: one or more DOM ID's (comma separated string or an array) to be updated as part of the ajax call. Default - no
17
- update.
18
-
19
- 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
20
- the same.
21
-
22
- - params: a hash of name/value pairs that will be converted to HTTP parameters in the ajax request
23
-
24
- - confirm: a message to be displayed in a JavaScript confirm dialog. By default there is no confirm dialog
25
-
26
- - message: a message to be displayed in the Ajax progress spinner. Default: "Saving..."
27
-
28
- - spinner-next-to: DOM ID of an element to position the ajax progress spinner next to.
29
-
30
- ### Ajax Callbacks
31
-
32
- The following attributes are also supported by all the ajax tags. Set them to fragments of javascript to have that script
33
- executed at various points in the ajax request cycle:
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. -->
45
- <def tag="hidden-fields" attrs="fields, for-query-string, skip"><%=
46
- pairs = if for_query_string
47
- query_params.to_a
48
- else
49
- hiddens = case fields
50
- when '*', nil
51
- # TODO: Need a better (i.e. extensible) way to eleminate certain fields
52
- this.class.column_names - ['type', 'created_at', 'updated_at']
53
- else
54
- comma_split(fields)
55
- end
56
- hiddens.map do |field|
57
- val = this.send(field)
58
- param_name = param_name_for(form_field_path + [field])
59
- [param_name, val] unless val.nil? ||
60
- field.to_sym.in?(this.class.attr_protected) ||
61
- (this.new_record? && val == this.class.column(field).default)
62
- end.compact
63
- end
64
- skip = comma_split skip
65
- pairs.reject! { |p| p.first.in?(skip) }
66
- pairs.map { |n, v| hidden_field_tag(n, v.to_s) if v && n.not_in?(scope.form_field_names) }.compact.safe_join("\n".html_safe)
67
- %></def>
68
-
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
- ### Additional Notes
104
-
105
- - Hobo automatically inserts an `auth_token` hidden field if forgery protection is enabled
106
-
107
- - Hobo inserts a `page_path` hidden field in create / update forms which it uses to re-render the correct page if a
108
- validation error occurs.
109
-
110
- - `<form>` supports all of the standrd ajax attributes - (see the main taglib docs for Rapid Forms)
111
-
112
- - `<form>` resets `last_if` if it does not have permission to display the form. The `<else>` clause may be used to display alternate content. For example:
113
-
114
- <form>...</form>
115
- <else>You do not have permission to edit this form</else>
116
-
117
- or on a standard generated page using a default form:
118
-
119
- <some-page>
120
- <after-form:>
121
- <else>You do not have permission to edit this form</else>
122
- </after-form:>
123
- </some-page>
124
-
125
- ### Attributes
126
-
127
- - [all AJAX attributes](/api_taglibs/rapid_forms)
128
-
129
- - action: the controller action. Default is create or update as appropriate
130
-
131
- - method: PUT or POST
132
-
133
- - web-method
134
-
135
- - lifecycle
136
-
137
- - owner
138
-
139
- - multipart: if set, the encoding is set to multipart/form-data. The default is x-www-form-urlencoded
140
-
141
- - reset-form: Clear the form after submission (only makes sense for ajax forms)
142
-
143
- - refocus-form: Refocus the first form-field after submission (only makes sense for ajax forms)
144
-
145
- ### Parameters
146
-
147
- The standard form tag does not have any parameters, nor does it have any default content. However, Hobo does autogenerate polymorphic form tags for each of your models into `app/views/taglibs/auto/rapid/forms.dryml`. These forms have the following parameters:
148
-
149
- - error-messages
150
-
151
- - field-list
152
-
153
- - actions
154
-
155
- - submit
156
-
157
- - cancel
158
-
159
- -->
160
- <def tag="form" polymorphic attrs="update, hidden-fields, action, method, web-method, lifecycle, owner, multipart"><%=
161
- ajax_attrs, html_attrs = attributes.partition_hash(Hobo::Rapid::Helper::AJAX_ATTRS)
162
- html_attrs[:enctype] ||= "multipart/form-data" if multipart
163
-
164
- new_record = this.try.new_record?
165
-
166
- method = if method.nil?
167
- (action || web_method || new_record) ? "post" : "put"
168
- else
169
- method.downcase
170
- end
171
-
172
- html_attrs[:action] = action || begin
173
- target = if owner
174
- collection_name = this.class.reverse_reflection(owner).name
175
- this.send(owner).send(collection_name)
176
- else
177
- this
178
- end
179
- action = web_method || lifecycle
180
- object_url(target, action, :method => method)
181
- end
182
-
183
- if action.nil? && (html_attrs[:action].nil? ||
184
- (lifecycle.nil? && new_record && !this.creatable_by?(current_user)) ||
185
- (lifecycle.nil? && !new_record && !can_edit?))
186
- Dryml.last_if = false
187
- ""
188
- else
189
- if method == "put"
190
- # browsers don't support put -- use post and add the Rails _method hack
191
- http_method_hidden = hidden_field_tag("_method", "PUT")
192
- html_attrs[:method] = "post"
193
- else
194
- http_method_hidden = ""
195
- html_attrs[:method] = method
196
- end
197
-
198
- if update || !ajax_attrs.empty?
199
- # add an onsubmit to convert to an ajax form if `update` is given
200
- function = ajax_updater(:post_form, update, ajax_attrs)
201
- html_attrs[:onsubmit] = [html_attrs[:onsubmit], "#{function}; return false;"].compact.safe_join("; ".html_safe)
202
- end
203
-
204
- hiddens = ""
205
- body = with_form_context do
206
- # It is important to evaluate parameters.default first, in order to populate scope.form_field_names
207
- b = parameters.default
208
- hiddens = self.hidden_fields(:fields => hidden_fields) if new_record
209
- b
210
- end
211
-
212
- auth_token = if method.nil? || method == 'get' || !protect_against_forgery?
213
- ''
214
- else
215
- element(:input, {:type => "hidden",
216
- :name => request_forgery_protection_token.to_s,
217
- :value => form_authenticity_token}, nil, true, true)
218
- end
219
-
220
- unless method == "get"
221
- page_path = if (request.post? || request.put?) && params[:page_path]
222
- params[:page_path]
223
- else
224
- request.fullpath
225
- end
226
- page_path_hidden = hidden_field_tag("page_path", page_path)
227
- end
228
-
229
- hiddens_div = element(:div, {:class => "hidden-fields"}, [http_method_hidden, page_path_hidden, auth_token, hiddens].safe_join)
230
-
231
- body = [hiddens_div, body].safe_join
232
-
233
- if action.nil? # don't add automatic css classes if the action was specified
234
- if web_method
235
- add_classes!(html_attrs, "#{type_id.dasherize}-#{web_method}-form")
236
- else
237
- add_classes!(html_attrs, "#{'new ' if new_record}#{type_id.dasherize}")
238
- end
239
- end
240
-
241
- Dryml.last_if = true
242
- element("form", html_attrs, body)
243
- end
244
- %></def>
245
-
246
- <!-- A shortcut for generating a submit button.
247
-
248
- ### Usage
249
-
250
- <submit label="Go!"/> -> <input type="submit" value="Go!" class="button submit-button"/>
251
- <submit image="/images/go.png"/> -> <input type="image" src="/images/go.png" class="button submit-button"/>
252
-
253
- -->
254
- <def tag="submit" attrs="label, image">
255
- <input if="&image" type="image" src="&image" merge-attrs class="image-button submit-button"/>
256
- <else>
257
- <input type="submit" value="#{label}" merge-attrs class="button submit-button"/>
258
- </else>
259
- </def>
260
-
261
- <!--
262
- Provides an editable control tailored to the type of the object in context. `<input>` tags should be used within a
263
- `<form>`. `<input>` is a _polymorphic_ tag which means that there are a variety of definitions, each one written for a
264
- particular type. For example there are inputs for `text`, `boolean`, `password`, `date`, `datetime`, `integer`,
265
- `float`, `string` and more.
266
-
267
- ### Usage
268
-
269
- The tag behaves as a regular HTML input if the type attribute is given:
270
-
271
- <input type="text" name="my_input"/> -> Output is exactly as provided, untouched by Rapid
272
-
273
- If no type attribute is given then the _context_ is used. For example if the context is a blog post:
274
-
275
- <input:title/> ->
276
- <input id="blog_post[name]" class="string blog-post-name" type="text" value="My Blog Post" name="blog_post[name]"/>
277
-
278
- <input:created_at/> ->
279
- <select id="blog_post_created_at_year" name="blog_post[created_at][year]">...</select>
280
- <select id="blog_post_created_at_month" name="blog_post[created_at][month]">...</select>
281
- <select id="blog_post_created_at_day" name="blog_post[created_at][day]">...</select>
282
-
283
- <input:description/> ->
284
- <textarea class="text blog-post-description" id="blog_post[description]" name="blog_post[description]">...</textarea>
285
-
286
- If the context is a `belongs_to` association, the `<select-one>` tag is used.
287
-
288
- If the context is a `has_many :through` association, the polymorphic `<collection-input>` tag is used.
289
-
290
- ### Attributes
291
-
292
- - no-edit: control what happens if `can_edit?` is false. Can be one of:
293
-
294
- - view: render the current value using the `<view>` tag
295
- - disable: render the input as normal, but add HTML's `disabled` attribute
296
- - skip: render nothing at all
297
- - ignore: render the input normally. That is, don't even perform the edit check.
298
- -->
299
- <def tag="input" attrs="no-edit"><%=
300
- if attributes[:type]
301
- element :input, attributes, nil, true, true
302
- else
303
- no_edit ||= :view
304
- no_edit = no_edit.to_sym
305
- no_edit_permission = !can_edit? unless no_edit == :ignore
306
- if no_edit_permission && no_edit == :view
307
- view
308
- elsif no_edit_permission && no_edit == :skip
309
- ""
310
- else
311
- attrs = add_classes(attributes, type_id.dasherize, type_and_field.dasherize)
312
- attrs[:name] ||= param_name_for_this
313
- attrs[:disabled] = true if no_edit_permission && no_edit == :disable
314
- the_input = if (refl = this_field_reflection)
315
- if refl.macro == :belongs_to
316
- call_polymorphic_tag('input', attrs) or select_one(attrs)
317
- elsif refl.macro == :has_many
318
- if refl.options[:through]
319
- collection_input(attrs)
320
- else
321
- input_many(attrs)
322
- end
323
- end
324
- else
325
- call_polymorphic_tag('input', attrs) or
326
- (call_polymorphic_tag('input', HoboFields.to_class(this_type::COLUMN_TYPE), attrs) if defined?(this_type::COLUMN_TYPE)) or
327
- raise Hobo::Error, ("No input tag for #{this_field}:#{this_type} (this=#{this.inspect})")
328
- end
329
- unless this_parent.errors[this_field].empty?
330
- "<span class='field-with-errors'>#{the_input}</span>".html_safe
331
- else
332
- the_input
333
- end
334
- end
335
- end
336
- %></def>
337
-
338
-
339
- <!-- This tag is called by `<input>` when the context is a `has_many :through` collection. By default a `<select-many>`
340
- is used, but this can be customised on a per-type basis. For example, say you would like the `<check-many>` tag used to
341
- edit collections a `Category` model in your application:
342
-
343
- <def tag="collection-input" for="Category"><check-many merge/></def>
344
- -->
345
- <def tag="collection-input" polymorphic></def>
346
-
347
- <!-- The default `<collection-input>` - calls `<select-many>` -->
348
- <def tag="collection-input" for="ActiveRecord::Base"><select-many merge/></def>
349
-
350
-
351
- <!-- A `<textarea>` input -->
352
- <def tag="input" for="text" attrs="name">
353
- <%= text_area_tag(name, this, attributes) %>
354
- </def>
355
-
356
- <!-- 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) -->
357
- <def tag="input" for="boolean" attrs="name">
358
- <%= unless attributes[:disabled]
359
- cb_tag = check_box_tag(name, '1', this, attributes)
360
- cb_hidden_tag = hidden_field_tag(name, '0', :id => nil)
361
- cb_hidden_tag + cb_tag
362
- end %>
363
- </def>
364
-
365
- <!-- A password input - `<input type='password'>` -->
366
- <def tag="input" for="password" attrs="name">
367
- <%= password_field_tag(name, this, attributes) %>
368
- </def>
369
-
370
- <!-- A date picker, using the `select_date` helper from Rails
371
-
372
- ### Attributes
373
-
374
- - all the options of select_date and date_select are passed to the select_date Rails
375
- helper
376
-
377
- All the other attributes are passed to the `select_date` helper as the html-options hash.
378
-
379
- The menus default to the current date if the current value is nil.
380
-
381
- Examples:
382
-
383
- - override the input for date tag in a form
384
-
385
- <my-special-date-view:>
386
- <input start-year="&1940" order="day,month,year" />
387
- </my-special-date-view:>
388
-
389
- - override the tag application-wide
390
-
391
- <extend tag='input' for='date'>
392
- <old-input merge order="day,month,year" start-year="&1940" />
393
- </extend>
394
-
395
- -->
396
- <def tag="input" for="date" attrs="use-month-numbers, use-short-month, add-month-numbers, use-month-names, date-separator, start-year, end-year, discard-day, discard-month, discard-year, order, include-blank, default, disabled, prompt, prefix">
397
- <% order = order.nil? ? [:year, :month, :day] : comma_split(order).*.to_sym -%>
398
- <%= select_date(this || current_time,
399
- (all_attributes - attributes.keys).reverse_merge(:prefix => param_name_for_this).merge(:order => order),
400
- attributes - [:name]) %>
401
- </def>
402
-
403
-
404
- <!-- A date/time picker, using the `select_time` helper from Rails
405
-
406
- ### Attributes
407
-
408
- - include-seconds, time-separator, prompt and prefix are passed to the select_time helper as options
409
-
410
- All the other attributes are passed to the `select_time` helper as the html-options hash.
411
-
412
- The menus default to the current time if the current value is nil.
413
-
414
- -->
415
- <def tag="input" for="time" attrs="include-seconds, time-separator, prompt, prefix">
416
- <%= select_time( this || current_time,
417
- (all_attributes - attributes.keys).reverse_merge(:prefix => param_name_for_this),
418
- attributes - [:name] ) %>
419
- </def>
420
-
421
-
422
- <!-- A date/time picker, using the `select_datetime` helper from Rails
423
-
424
- ### Attributes
425
-
426
- - order: The order of the year, month and date menus. A comma separated string or an array. Default: "year, month,
427
- day, hour, minute"
428
- - date-separator, discard-type, prompt and prefix are passed to the select_date helper as options
429
-
430
- All the other attributes are passed to the `select_date` helper as the html-options hash.
431
-
432
- The menus default to the current time if the current value is nil.
433
-
434
- -->
435
- <def tag="input" for="datetime" attrs="order, date-separator, discard-type, prompt, prefix">
436
- <% if ! order.nil?
437
- order = comma_split(order).*.to_sym
438
- attributes.merge!(:order => order)
439
- end -%>
440
- <%= select_datetime(this || current_time,
441
- (all_attributes - attributes.keys).reverse_merge(:prefix => param_name_for_this).merge(:order => order),
442
- attributes - [:name] ) %>
443
- </def>
444
-
445
- <!-- An `<input type='text'>` input. -->
446
- <def tag="input" for="integer" attrs="name">
447
- <%= text_field_tag(name, this, attributes) %>
448
- </def>
449
-
450
- <!-- An `<input type='text'>` input. -->
451
- <def tag="input" for="BigDecimal" attrs="name">
452
- <%= text_field_tag(name, this, attributes) %>
453
- </def>
454
-
455
- <!-- An `<input type='text'>` input. -->
456
- <def tag="input" for="float" attrs="name">
457
- <%= text_field_tag(name, this, attributes) %>
458
- </def>
459
-
460
- <!-- An `<input type='text'>` input. -->
461
- <def tag="input" for="string" attrs="name">
462
- <%= text_field_tag(name, this, attributes) %>
463
- </def>
464
-
465
- <!-- A `<select>` menu containing the values of an 'enum string'.
466
-
467
- ### Attributes
468
-
469
- - `labels` - A hash that gives custom labels for the values of the enum.
470
- Any values that do not have corresponding keys in this hash will get `value.titleize` as the label.
471
- - `titleize` - Set to false to have the value itself (rather than `value.titleize`) be the default label. Default: true
472
- - `first-option` - a string to be used for an extra option in the first position. E.g. "Please choose..."
473
- - `first-value` - the value to be used with the `first-option`. Typically not used, meaning the option has a blank value.
474
-
475
- -->
476
- <def tag="input" for="HoboFields::Types::EnumString" attrs="labels, titleize, first-option, first-value"><%
477
- labels ||= {}
478
- labels = HashWithIndifferentAccess.new(labels)
479
- titleize = true if titleize.nil?
480
- options = this_type.values.map {|v| [labels.fetch(v, titleize ? this_type.translated_values[v].titleize : this_type.translated_values[v]), v] }
481
- %>
482
- <select name="#{param_name_for_this}" merge-attrs>
483
- <option value="#{first_value}" unless="&first_option.nil?"><first-option/></option>
484
- <%= options_for_select(options, this) %>
485
- </select>
486
- </def>
487
-
488
-
489
- <!-- Provides either an ajax or non-ajax button to invoke a "remote method" or "web method" declared in the controller.
490
- Web Methods provide support for the RPC model of client-server interaction, in contrast to the REST model. The
491
- preference in Rails is to use REST as much as possible, but we are pragmatists, and sometimes you just to need a remote
492
- procedure call.
493
-
494
- The URL that the call is POSTed to is the `object_url` of `this`, plus the method name
495
-
496
- `<remote-method-button>` supports all of the standard ajax attributes (see the main taglib documention for Rapid
497
- Forms). If any ajax attributes are given, the button becomes an ajax button, if not, Rails' `button_to` is used, which behaves similarly to a standard link.
498
-
499
- ### Attributes
500
-
501
- - method: the name of the web-method to call
502
-
503
- - label: the label on the button
504
-
505
- -->
506
- <def tag="remote-method-button" attrs="method, update, label, confirm, url"><%=
507
- ajax_attributes, html_attributes = attributes.partition_hash(Hobo::Rapid::Helper::AJAX_ATTRS)
508
-
509
- url ||= object_url(this, method.to_s.gsub('-', '_'), :method => :post)
510
- raise ArgumentError, "no such web method '#{method}' on #{this.typed_id}" unless url
511
-
512
- add_classes!(html_attributes, "button remote-method-button #{method}-button")
513
- label ||= method.titleize
514
- if update || !ajax_attributes.empty?
515
- ajax_attributes[:message] ||= label
516
- func = ajax_updater(url, update, ajax_attributes.merge(:confirm => confirm))
517
- html_attributes.update(:onclick => "var e = this; " + func, :type =>'button', :value => label)
518
- element(:input, html_attributes, nil, true, true)
519
- else
520
- button_to(label, url, html_attributes.merge(:confirm => confirm))
521
- end
522
- %></def>
523
-
524
-
525
- <!-- Provides an ajax button to send a RESTful update or "PUT" to the server. i.e. to udate one or more fields of a
526
- record.
527
-
528
- Note that unlike simliar tags, `<update-button>` does not support both ajax and non-ajax modes at this time. It only
529
- does ajax.
530
-
531
- `<update-button>` supports all of the standard ajax attributes (see the main taglib documention for Rapid Forms).
532
-
533
- ### Attributes
534
-
535
- - label: The label on the button.
536
-
537
- - fields: A hash with new field values pairs to update the resource with. The items in the hash will be converted to
538
- HTTP parameters.
539
-
540
- - params: Another hash with additional HTTP parameters to include in the ajax request
541
-
542
- -->
543
- <def tag="update-button" attrs="label, update, fields, params"><%=
544
- raise Hobo::Error.new("no update specified") unless update
545
-
546
- ajax_attributes, html_attributes = attributes.partition_hash(Hobo::Rapid::Helper::AJAX_ATTRS)
547
- params = (params || {}).merge(this.class.name.underscore => fields)
548
- ajax_attributes.reverse_merge!(:message => label, :params => params, :method => :put)
549
- func = ajax_updater(object_url(this), update, ajax_attributes)
550
- html_attributes.reverse_merge!(:type =>'button', :onclick => func, :value => label)
551
-
552
- element :input, add_classes(html_attributes, "button update-button update-#{this.class.name.underscore}-button"), nil, true, true %>
553
- </def>
554
-
555
-
556
- <!-- Provides either an ajax or non-ajax delete button to send a RESTful "DELETE". The context should be a record for
557
- which you to want provide a delete button.
558
-
559
- The Rapid Library has a convention of marking (in the output HTML, using a special CSS class) elements as "object
560
- elements", with the class and ID of the ActiveRecord object that they represent. `<delete-button>` assumes it is placed
561
- inside such an element, and will automatically find the right element to remove (fade out) from the DOM. The
562
- `<collection>` tag adds this metadata (CSS class) automatically, so `<delete-button>` works well when used inside a
563
- `<collection>`. This is a Clever Trick which needs to be revisted and perhaps simplified.
564
-
565
- If used within a `<collection>`, `<delete-button>` also knows how to add an "empty message" such as "no comments to
566
- display" when you delete the last item. Clever Tricks abound.
567
-
568
- Current limitation: There is no support for the ajax callbacks at this time.
569
-
570
- All the standard ajax attributes *except the callbacks* are supported (see the main taglib documention for Rapid Forms).
571
-
572
-
573
-
574
- ### Attributes
575
-
576
- - label: The label for the button. Default: "Remove"
577
-
578
- - in-place: delete in place (ajax)? Default: true, or false if the record to be deleted is the same as the top level
579
- context of the page
580
-
581
- - image: URL of an image for the button. Changes the rendered tag from `<input type='button'>` to `<input type='image'
582
- src='...'>`
583
-
584
- - fade: Perform the fade effect (true/false)? Default: true
585
-
586
- -->
587
- <def tag="delete-button" attrs="label, update, in-place, image, confirm, fade, subsite"><%=
588
- in_place = false if in_place.nil? && this == @this && request.method.downcase == "get"
589
- url = object_url(this, :method => :delete, :subsite => subsite)
590
- if (Dryml.last_if = url && can_delete?)
591
- attributes = attributes.merge(if image
592
- { :type => "image", :src => "#{base_url}/images/#{image}" }
593
- else
594
- { :type => "button" }
595
- end)
596
- label ||= t("hobo.actions.remove", :default=>"Remove")
597
- confirm = t("hobo.messages.confirm", :default=>"Are you sure?") if confirm.nil?
598
-
599
- add_classes!(attributes,
600
- image ? "image-button" : "button",
601
- "delete-button delete-#{this.class.name.underscore.dasherize}-button")
602
- if url
603
- if in_place == false
604
- attributes[:confirm] = confirm if confirm
605
- attributes[:method] = :delete
606
- button_to(label, url, attributes)
607
- else
608
- fade = true if fade.nil?
609
- attributes[:value] = label
610
- attributes[:onclick] = "Hobo.removeButton(this, '#{url}', #{js_updates(update)}, {fade:#{fade}, confirm: '#{confirm}'})"
611
- element(:input, attributes, nil, true, true)
612
- end
613
- end
614
- else
615
- ""
616
- end
617
- %></def>
618
-
619
-
620
- <!-- Provides an ajax create button that will send a RESTful "POST" to the server to create a new resource.
621
-
622
- All of the standard ajax attributes are supported (see the main taglib documention for Rapid Forms).
623
-
624
- ### Attributes
625
-
626
- - model: The class to instantiate, pass either the class name or the class object.
627
-
628
- -->
629
- <def tag="create-button" attrs="model, update, label, fields, message"><%=
630
- raise Hobo::Error.new("no update specified") unless update
631
-
632
- fields ||= {}
633
- class_or_assoc = if model
634
- model.is_a?(String) ? model.constantize : model
635
- elsif Hobo.simple_has_many_association?(this)
636
- id_method = this_field_reflection.options[:primary_key] || this_field_reflection.klass.primary_key
637
- fields[this_field_reflection.primary_key_name] = this.proxy_owner.send(id_method)
638
- this
639
- else
640
- raise Hobo::Error.new("invalid context for <create-button>")
641
- end
642
- new = class_or_assoc.new(fields)
643
- new.set_creator(current_user)
644
- if can_create?(new)
645
- label ||= ht("#{new.class.to_s.underscore}.actions.new", :default=>"New #{new.class.model_name.human}")
646
- class_name = new.class.name.underscore
647
- params ||= params || {}
648
- params = params.merge(class_name => fields) unless fields.blank?
649
-
650
- ajax_attributes, html_attributes = attributes.partition_hash(Hobo::Rapid::Helper::AJAX_ATTRS)
651
-
652
- ajax_attributes.reverse_merge!(:message => message, :params => params)
653
- func = ajax_updater(object_url(new.class, :method => :post), update, ajax_attributes)
654
- html_attributes.reverse_merge!(:type =>'button', :onclick => func, :value => label)
655
-
656
- element :input, add_classes(html_attributes,
657
- "button create-button create-#{class_name}-button"), nil, true, true
658
- end
659
- %></def>
660
-
661
-
662
- <!-- A `<select>` menu from which the user can choose the target record for a `belongs_to` association.
663
-
664
- This is the default input that Rapid uses for `belongs_to` associations. The menu is constructed using the `to_s` representation of the records.
665
-
666
- ### Attributes
667
-
668
- - `include-none` - whether to include a 'none' option (i.e. set the foreign key to null). If this value is not supplied, the default is "true" if the current value is nil; otherwise the default is "false". One implication of this is that the default may change when the form is re-rendered due to a validation failure. Setting this value explicitly is recommended.
669
- - `blank-message` - the message for the 'none' option. Defaults to "(No `<model-name>`)", e.g. "(No Product)"
670
- - `options` - an array of records to include in the menu. Defaults to the all the records in the target table that match any `:conditions` declared on the `belongs_to` (subject to `limit`)
671
- - `sort` - whether to sort the array of options. Defaults to no sorting.
672
- - `limit` - if `options` is not specified, this limits the number of records. Default: 100
673
- - `text_method` - The method to call on each record to get the text for the option. Multiple methods are supported ie "institution.name"
674
-
675
- ### See Also
676
-
677
- For situations where there are too many target records to practically include in a menu, `<name-one>` provides an autocompleter which would be more suitable.
678
-
679
- -->
680
- <def tag="select-one" attrs="include-none, blank-message, options, sort, limit, text-method"><%
681
- raise Hobo::PermissionDeniedError.new("Not allowed to edit #{this_field}") if !attributes[:disabled] && !can_edit?
682
- blank_message ||= ht("#{this_type.name.underscore}.messages.none", :default=>"No #{this_type.model_name.human} available.")
683
- limit ||= 100
684
-
685
- options ||= begin
686
- conditions = ActiveRecord::Associations::BelongsToAssociation.new(this_parent, this_field_reflection).send(:conditions)
687
- order = this_field_reflection.klass.default_order
688
- this_field_reflection.klass.all(:conditions => conditions, :limit => limit, :order => order).select {|x| can_view?(x)}
689
- end
690
-
691
- id_method = this_field_reflection.options[:primary_key] || this_field_reflection.klass.primary_key
692
- if text_method.nil?
693
- select_options = options.map { |x| [name(:with => x, :no_wrapper => true), x.send(id_method)] }
694
- else
695
- select_options = options.map do |x|
696
- [ text_method.split(".").inject(x) { |v, method| v.send(method) },
697
- x.send(id_method) ]
698
- end
699
- end
700
- select_options = select_options.sort if sort
701
- select_options.insert(0, [blank_message, ""]) if include_none || (this.nil? && include_none != false)
702
- attributes = add_classes(attributes, "input", "belongs_to", type_and_field)
703
- -%>
704
- <select name="#{param_name_for_this(true)}" merge-attrs="&attributes.except :name">
705
- <%= options_for_select(select_options, this ? this.send(id_method) : "") %>
706
- </select>
707
- </def>
708
-
709
-
710
- <!-- An `<input type="text">` with auto-completion. Allows the user to chose the target of a `belongs_to` association by name.
711
-
712
- This tag relies on an autocompleter being defined in a controller. A simple example:
713
-
714
- <form with="&ProjectMembership.new">
715
- <name-one:user>
716
- </form>
717
-
718
- class ProjectMembership < ActiveRecord::Base
719
- hobo_model
720
- belongs_to :user
721
- end
722
-
723
- class User < ActiveRecord::Base
724
- hobo_user_model
725
- has_many :project_memberships, :accessible => true, :dependent => :destroy
726
- end
727
-
728
- class UsersController < ApplicationController
729
- autocomplete
730
- end
731
-
732
- The route used by the autocompleter looks something like `/users/complete_name`. The first part of this route is specified by the `complete-target` attribute, and the second part is specified by the `completer` attribute.
733
-
734
- `complete-target` specifies the controller for the route. It can be specified by either supplying a model class or a model. If a model is supplied, the id of the model is passed as a parameter to the controller. (`?id=7`, for example) The default for this attribute is the class of the context. In other words, the class that contains the `has_many / has_one`, not the class with the `belongs_to`.
735
-
736
- `completer` specifies the action for the route. `name-one` prepends `complete_` to the value given here. This should be exactly the same as the first parameter to `autocomplete` in your controller. As an example: `autocomplete :email_address` would correspond to `completer="email_address"`. The default for this attribute is the name field for the model being searched, which is usually `name`, but not always.
737
-
738
- The query string is passed to the controller in the `query` parameter. (`?query=hello` for example).
739
-
740
- For more information on how to customize the controller, see the [controller manual](http://cookbook.hobocentral.net/manual/controllers#autocompleters)
741
-
742
- Here's a more complex example. This used to be a part of [agility](http://cookbook.hobocentral.net/tutorials/agility) until it was simplified.
743
-
744
- class ProjectsController < ApplicationController
745
- autocomplete :new_member_name do
746
- project = find_instance
747
- hobo_completions :name, User.without_project(project).is_not(project.owner)
748
- end
749
- end
750
-
751
- Note that this was added to the projects controller, rather than the users controller as in the first example. You can read this as: create an auto-complete action called `new_member_name` that finds users that are not already members of the project, and not the owner of the project, and completes the :name field.
752
-
753
- <name-one:user complete-target="&@project" completer="new_member_name"/>
754
-
755
- We're using an object as the complete-target rather than a class. This allows the `find_instance` in our controller action to function.
756
-
757
- There's another example of `<name-one>` use in the [recipes](http://cookbook.hobocentral.net/recipes/36-using-a-name-one-one-a).
758
-
759
- ### Attributes:
760
-
761
- - `complete-target`, `completer`: see above
762
- - `min-chars`: The minimum number of characters that must be entered in the input field before an Ajax request is made.
763
- - `nil-value`: If there is no current value, this text will appear greyed out inside the control, and will disappear on focus.
764
-
765
- ### Note:
766
-
767
- If you wish to set `min-chars` to 0, you will require this [patch to controls.js](http://github.com/bryanlarsen/scriptaculous/commit/3915b7b). 'controls.js' was added to your project via the rails generator, not via Hobo.
768
-
769
- -->
770
- <def tag="name-one" attrs="complete-target, completer, min-chars, nil-value"><%
771
- complete_target ||= this_field_reflection.klass
772
- completer ||= (complete_target.is_a?(Class) ? complete_target : complete_target.class).name_attribute
773
- min_chars ||= 1
774
- value = name(:no_wrapper => true, :if_present => true)
775
- -%>
776
- <wrap tag="span" class="field-with-errors" when="&!this_parent.nil? && !this_parent.errors[this_field].empty?">
777
- <input type="text" name="#{param_name_for_this}"
778
- class="autocompleter #{type_and_field._?.dasherize} #{css_data :complete_on, typed_id(complete_target), completer} #{css_data :min_chars, min_chars} #{'nil-value' if value==''}"
779
- value="#{value=='' ? nil_value : value}"
780
- merge-attrs/>
781
- <div class="completions-popup" style="display:none"></div>
782
- </wrap>
783
- </def>
784
-
785
-
786
- <!-- nodoc. -->
787
- <def tag="sti-type-input">
788
- <select name="#{param_name_for(form_field_path + ['type'])}">
789
- <%= options_for_select(this.class.send(:descendants).map{|x| [x.name.titleize, x.name]}, this.class.name) %>
790
- </select>
791
- </def>
792
-
793
-
794
- <!-- A `<select>` menu input. This tag differes from `<select-menu>` only in that it adds the correct `name` attribute for the current field, and `selected` default to `this`.
795
-
796
- ### Attributes
797
-
798
- - `options` - an array of options suitable to be passed to the Rails `options_for_select` helper.
799
- - `selected` - the value (from the `options` array) that should be initially selected. Defaults to `this`
800
- - `first-option` - a string to be used for an extra option in the first position. E.g. "Please choose..."
801
- - `first-value` - the value to be used with the `first-option`. Typically not used, meaning the option has a blank value.
802
-
803
- -->
804
- <def tag="select-input">
805
- <select-menu name="#{param_name_for_this}" selected="&this" merge/>
806
- </def>
807
-
808
-
809
- <!-- Renders a localized list of error messages following a form submission. Expects the errors to be in `this.errors`. Renders nothing if there are no errors.
810
- -->
811
- <def tag="error-messages">
812
- <section class="error-messages" merge-attrs if="&this.errors.size > 0">
813
- <h2 param="heading">
814
- <t key="activerecord.errors.template.header" model="&this.class.model_name.human" count="&this.errors.size">
815
- <%= model = this.class.model_name.human
816
- count = this.errors.size
817
- count==1 ? "1 error prohibited this #{model} from being saved" :
818
- "#{count} errors prohibited this #{model} from being saved"
819
- %>
820
- </t>
821
- </h2>
822
- <t key="activerecord.errors.template.body">There were problems with the following fields:</t>
823
- <ul param>
824
- <% this.errors.to_a.each do |message| -%>
825
- <li param><%= message %></li>
826
- <% end -%>
827
- </ul>
828
- </section>
829
- </def>
830
-
831
-
832
- <!--
833
- An input for `has_many :through` associations that lets the user chose the items from a `<select>` menu.
834
-
835
- To use this tag, the model of the items the user is chosing *must* have unique names, and the
836
- -->
837
- <def tag="select-many" attrs="options, targets, remove-label, prompt, disabled, name"><%
838
- prompt ||= ht("#{this_field_reflection.klass.to_s.underscore}.form.select_many.prompt", :default=>"Add #{this_field.titleize.singularize}")
839
- options ||= this_field_reflection.klass.all(:conditions =>this.send(:conditions)).select {|x| can_view?(x)}
840
- name ||= param_name_for_this
841
-
842
- values = this
843
- remove_label ||= t("hobo.actions.remove", :default=>'Remove')
844
- -%>
845
- <div class="input select-many" merge-attrs>
846
- <div style="display:none" class="item-proto">
847
- <div class="item" param="proto-item">
848
- <span></span>
849
- <input type="hidden" name="#{name}[]" param="proto-hidden"/>
850
- <input type="button" class="remove-item" value="#{remove_label}" param="proto-remove-button"/>
851
- </div>
852
- </div>
853
- <div class="items">
854
- <div class="item" param="item" repeat>
855
- <span><%= h this.to_s %></span>
856
- <input type="hidden" name="#{name}[]" value="@#{h this.id}" disabled="&disabled"
857
- param="hidden"/>
858
- <input type="button" class="remove-item" value="#{remove_label}" disabled="&disabled"
859
- param="remove-button"/>
860
- </div>
861
- </div>
862
- <select merge-attrs="&{:disabled => disabled}">
863
- <option value=""><prompt/></option>
864
- <repeat with="&options">
865
- <if test="&this.in?(values)">
866
- <optgroup class="disabled-option" label="#{h this.to_s}" alt="@#{this.id}">&nbsp;</optgroup>
867
- </if>
868
- <else>
869
- <option value="@#{this.id}"><%= h this.to_s %></option>
870
- </else>
871
- </repeat>
872
- </select>
873
- </div>
874
- </def>
875
-
876
-
877
- <!--
878
- 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.
879
-
880
- ### Usage
881
-
882
- Use the `stay-here` attribute to remain on the current page:
883
-
884
- <form>
885
- <after-submit stay-here/>
886
- ...
887
- </form>
888
-
889
- Use the `go-back` option to return to the page in `session[:previous_uri]`:
890
-
891
- <form>
892
- <after-submit go-back/>
893
- ...
894
- </form>
895
-
896
- Use the `uri` option to specify a redirect location:
897
-
898
- <form>
899
- <after-submit uri="/admin"/>
900
- ...
901
- </form>
902
- -->
903
- <def tag="after-submit" attrs="uri, stay-here, go-back"><%
904
- uri = "stay-here" if stay_here
905
- uri = session[:previous_uri] if go_back
906
- -%>
907
- <input type="hidden" value="&params[:after_submit] || uri" name="after_submit" if="&uri"/>
908
- </def>
909
-
910
-
911
- <!-- A simple wrapper around the `<select>` tag and `options_for_select` helper
912
-
913
- ### Attributes
914
-
915
- - `options` - an array of options suitable to be passed to the Rails `options_for_select` helper.
916
- - `selected` - the value (from the `options` array) that should be initially selected. Defaults to `this`
917
- - `first-option` - a string to be used for an extra option in the first position. E.g. "Please choose..."
918
- - `first-value` - the value to be used with the `first-option`. Typically not used, meaning the option has a blank value.
919
- - `key` - the key used to lookup in the locale file or 'default' by default. If you pass it hobo will lookup in the namespace "tags.select_menu.#{key}" in order to find `options`, `first_option` and `first_value`. The passed attributes are used as a default in case the lookups fail. (see the documentation of filter-menu tag for a similar example).
920
- -->
921
- <def tag="select-menu" attrs="options, selected, first-option, first-value, key">
922
- <% key ||= 'default'
923
- %w[options first_option first_value].each do |a|
924
- str = t("tags.select_menu.#{key}.#{a}", :default=>'')
925
- eval "#{a} = str unless str.blank?"
926
- end
927
- -%>
928
- <select merge-attrs param="default">
929
- <% selected=this if selected.nil? %>
930
- <option value="#{first_value}" unless="&first_option.nil?"><first-option/></option>
931
- <do param="options"><%= options_for_select(options, selected) %></do>
932
- </select>
933
- </def>
934
-
935
-
936
- <!-- Renders a `<ul>` list of checkboxes, one for each of the potential targt in a `has_many` association. The user can check the items they wish to have associated. A typical use might be selecting categories for a blog post.
937
-
938
- ### Attributes
939
-
940
- - `options` - an array of models that may be added to the collection
941
- - `disabled` - if true, sets the disabled flag on all check boxes.
942
-
943
- -->
944
- <def tag="check-many" attrs="options, disabled"><%
945
- collection = this
946
- param_name = param_name_for_this
947
- options ||= begin
948
- conditions = ActiveRecord::Associations::BelongsToAssociation.new(this_parent, this_field_reflection).send(:conditions)
949
- this_field_reflection.klass.all(:conditions => conditions, :limit => 100).select {|x| can_view?(x)}
950
- end
951
- -%>
952
- <ul class="check-many" param="default" merge-attrs>
953
- <input type="hidden" name="#{param_name}[]" value=""/><% # ensure all items are removed when nothing checked
954
- %>
955
- <li repeat="&options" param>
956
- <input id="#{dom_id(this, :check_many)}" type="checkbox" name="#{param_name}[]" value="@#{this.id}" checked="&this.in?(collection)" disabled="&disabled"/>
957
- <label for="#{dom_id(this, :check_many)}"><name param/></label>
958
- </li>
959
- </ul>
960
- </def>
961
-
962
-
963
- <!-- Renders an `<input type='hidden'>` for the `id` field of the current context -->
964
- <def tag="hidden-id-field">
965
- <if:id><input type="hidden" name="#{param_name_for_this}" value="#{this}" /></if>
966
- </def>
967
-
968
-
969
- <!-- Creates a sub-section of the form which the user can repeat using (+) and (-) buttons, in order to allow an entire `has_many` collection to be created/edited in a single form.
970
-
971
- This tag is very different from tags like `<select-many>` and `<check-many>` in that:
972
-
973
- - Those tags are used to *choose existing records* to include in the association, while `<input-many>` is used to actually create or edit the records in the association.
974
-
975
- ### Example
976
-
977
- Say you are creating a new `Category` in your online shop, and you want to create some initial products *in the same form*, you can add the following to your form:
978
-
979
- <input-many:products><field-list fields="name, price"/></input-many>
980
-
981
- The body of the tag will be repeated for each of the current records in the collection, or will just appear once (with blank fields) if the colleciton is empty.
982
-
983
- ### Attributes
984
-
985
- - fields: If you do not specify any content for the input-many, a `<field-list>` is rendered. This attribute is passed through to the `<field-list>`
986
-
987
- - skip: Passed through to the `<field-list>`. If not specified, it defaults to the parent association.
988
-
989
- ### Example
990
-
991
- Say you are creating a new `Category` in your online shop, and you want to create some initial products *in the same form*, you can add the following to your form:
992
-
993
- <hjq-input-many:products fields="name, price" />
994
-
995
- You'll often want to provide the `item` parameter:
996
-
997
- <hjq-input-many:products><item:><field-list fields="name, price" /></item:></hjq-input-many>
998
-
999
- A fully worked up example of nested hjq-input-many's may be found in [agility/jquery-test](http://github.com/tablatom/agility/blob/jquery-test/app/views/projects/nested_has_many_test.dryml)
1000
-
1001
- ### Attributes
1002
-
1003
- - `minimum`: the minimum number of items in the collection. Currently only '0' and '1' are supported values. The default is '0'.
1004
-
1005
- - `fields`, `skip`: passed down to the `field-list` tag in the default `item`.
1006
-
1007
- - `template`: the default values for new items. Normally this functionality is better provided by Model.new, but it's here if you need it.
1008
-
1009
- ### Events
1010
-
1011
- - `rapid:add`: fired after the element is inserted. `memo.element`
1012
- is set to the new element inserted.
1013
-
1014
- - `rapid:remove`: fired before the element is
1015
- inserted. `memo.element` is set to the element to be deleted. The
1016
- removal may be cancelled by stopping the event.
1017
-
1018
- - `rapid:change`: fired after an element has been removed or
1019
- inserted. `memo.element` set as above.
1020
-
1021
- Example javascript:
1022
-
1023
- var last_added;
1024
- var last_removed;
1025
- Event.addBehavior({
1026
- '.stories:rapid:add' : function(ev) {
1027
- last_added = ev.memo.element;
1028
- },
1029
- '.stories:rapid:remove' : function(ev) {
1030
- if(!confirm("really?")) ev.stop();
1031
- }
1032
- });
1033
-
1034
- Note: if your javascript does not work, please ensure that you have
1035
- the Hobo version of lowpro.js.
1036
-
1037
- -->
1038
- <def tag="input-many" attrs="minimum, fields, skip, more-skip, template" polymorphic >
1039
- <%
1040
- # helper function to create id's on buttons to facilitate testing
1041
- def underize(s)
1042
- s.gsub(/\[/,"_").gsub(/\]/,"")
1043
- end
1044
- %>
1045
- <set empty="&this.empty?"/>
1046
- <% template ||= this.try.new_candidate || this.member_class.new %>
1047
- <% minimum ||= 0 ; minimum = minimum.to_i %>
1048
- <% skip ||= this.proxy_reflection.klass.reflect_on_all_associations.detect {|p| p.primary_key_name==this.proxy_reflection.primary_key_name}.try.name.to_s if this.respond_to? :proxy_reflection %>
1049
- <% skip += ",#{more_skip}" if more_skip -%>
1050
- <ul class="input-many #{this_field.dasherize} #{css_data :input_many_prefix, param_name_for_this} #{css_data(:minimum, minimum)}" merge-attrs>
1051
- <fake-field-context fake-field="-1" context="&template">
1052
- <li class="input-many-li input-many-template" id="#{underize param_name_for_this}">
1053
- <div class="input-many-item" param="default">
1054
- <field-list param merge-attrs="fields" skip="&skip" />
1055
- </div>
1056
- <div class="buttons">
1057
- <button param="remove-item" id="#{underize param_name_for_this}_remove">-</button>
1058
- <button param="add-item" id="#{underize param_name_for_this}_add">+</button>
1059
- </div>
1060
- </li>
1061
- </fake-field-context>
1062
- <li class="input-many-li empty #{'hidden' unless this.empty? and minimum==0}" id="#{underize param_name_for_this}_-1_empty">
1063
- <!-- HACK way to signal an empty collection to the controller -->
1064
- <input type="hidden" class="empty-input" id="#{underize param_name_for_this}" name="#{param_name_for_this}" value="" disabled="&(!this.empty? || minimum>0)" />
1065
- <fake-field-context fake-field="-1" context="&template">
1066
- <div param="empty-message">
1067
- <ht key="#{this.class.to_s.underscore}.collection.empty_message">
1068
- No <%= this.class.name.titleize.downcase.pluralize %>.
1069
- </ht>
1070
- </div>
1071
- <div class="buttons">
1072
- <button param="remove-item" class="hidden" id="#{underize param_name_for_this}_remove">-</button>
1073
- <button param="add-item" id="#{underize param_name_for_this}_add">+</button>
1074
- </div>
1075
- </fake-field-context>
1076
- </li>
1077
- <fake-field-context fake-field="0" context="&template">
1078
- <li class="input-many-li" if="&(this_parent.empty? && minimum>0)" id="#{underize param_name_for_this}">
1079
- <div class="input-many-item" param="default">
1080
- <field-list param merge-attrs="fields" skip="&skip" />
1081
- </div>
1082
- <div class="buttons">
1083
- <button param="remove-item" class="hidden" id="#{underize param_name_for_this}_remove">-</button>
1084
- <button param="add-item" id="#{underize param_name_for_this}_add">+</button>
1085
- </div>
1086
- </li>
1087
- </fake-field-context>
1088
- <li repeat class="input-many-li #{'record-with-errors' unless this.errors.empty?}" id="#{underize param_name_for_this}">
1089
- <error-messages without-heading class="sub-record"/>
1090
- <hidden-id-field/>
1091
- <div class="input-many-item" param="default">
1092
- <field-list param merge-attrs="fields" skip="&skip" />
1093
- </div>
1094
- <div class="buttons">
1095
- <button param="remove-item" class="#{'hidden' if this_parent.length<=minimum}" id="#{underize param_name_for_this}_remove">-</button>
1096
- <button param="add-item" class="#{'hidden' if not last_item?}" id="#{underize param_name_for_this}_add">+</button>
1097
- </div>
1098
- </li>
1099
- </ul>
1100
- </def>
1101
-
1102
- <!-- Renders a sub-section of a form with fields for every record in a `has_many` association. This is similar to `<input-many>` except there is no ability to add and remove items (i.e. no (+) and (-) buttons).
1103
- -->
1104
- <def tag="input-all">
1105
- <% association_fkey = this_field_reflection.primary_key_name -%>
1106
- <ul class="input-all #{this_field.dasherize}">
1107
- <li repeat class="#{'record-with-errors' unless this.errors.empty?}">
1108
- <set-scoped form-field-names="&[]">
1109
- <hidden-id-field/>
1110
- <do param="default"/>
1111
- <hidden-fields skip="&association_fkey"/>
1112
- </set-scoped>
1113
- </li>
1114
- </ul>
1115
- </def>
1116
-
1117
- <!-- An enhanced version of [`<input-many>`](/api_tag_defs/input-many) that supports drag-and-drop re-ordering.
1118
-
1119
- Each item in the collection has a `<div class="ordering-handle" param="handle">` added, which can be used to drag the item up and down.
1120
-
1121
- If the items in the collection contain an [`acts_as_list`](http://ar.rubyonrails.org/classes/ActiveRecord/Acts/List/ClassMethods.html) declaration, it is updated.
1122
-
1123
- The specified sort order may be maintained even without `acts_as_list`. The items will be passed to the controller in the correct order, so the order may be persisted there.
1124
-
1125
- ### Attributes
1126
-
1127
- - `id`: Due to a limitation in script.aculo.us, an id is required. If you do not supply one, one will be generated.
1128
- - `position-column`: The position column may be specified via `acts_as_list`, via a `position_column` method on your model or via this attribute.
1129
- - others: all other attributes are passed through to `<input-many>`
1130
-
1131
- -->
1132
-
1133
- <def tag="sortable-input-many" attrs="id, position-column, template">
1134
- <% this_id = this_parent.id || rand(100000) -%>
1135
- <% id ||= "sortable-input-many-#{this_parent.class.name.underscore.dasherize}-#{this_id}-#{this_field_reflection.name}" -%>
1136
- <% template ||= this.try.new_candidate || this.member_class.new %>
1137
- <% position_column ||= template.try.position_column -%>
1138
- <input-many merge id="&id" class="sortable-input-many" template="&template" more-skip="&position_column">
1139
- <default: replace>
1140
- <div class="ordering-handle" param="handle" if="&can_edit?">&uarr;<br/>&darr;</div>
1141
- <if test="&position_column">
1142
- <input class="sortable-position" type="hidden" value="&this.send(position_column)" name="#{param_name_for_this}[#{position_column}]" />
1143
- </if>
1144
- <default restore/>
1145
- </default:>
1146
- </input-many>
1147
- </def>
1148
-
1149
- <!-- Renders the common "or (Cancel)" for a form. Attributes are merged into the link (`<a>Cancel</a>`), making it easy to customise the destination of the cancel link. By default it will link to `this` or `this.class`.
1150
- -->
1151
- <def tag="or-cancel">
1152
- <if test="&linkable?"><t key="hobo.support.or">or</t> <a merge-attrs><t key="hobo.actions.cancel">Cancel</t></a></if>
1153
- <else>
1154
- <if test="&linkable?(this.class)"><t key="hobo.support.or">or</t> <a to="&this.class" merge-attrs><t key="hobo.actions.cancel">Cancel</t></a></if>
1155
- </else>
1156
- </def>