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.
- data/CHANGES.txt +330 -0
- data/Manifest +12 -4
- data/Rakefile +4 -6
- data/dryml_generators/rapid/cards.dryml.erb +5 -1
- data/dryml_generators/rapid/forms.dryml.erb +8 -10
- data/dryml_generators/rapid/pages.dryml.erb +65 -36
- data/hobo.gemspec +28 -15
- data/lib/active_record/association_collection.rb +3 -22
- data/lib/hobo.rb +25 -258
- data/lib/hobo/accessible_associations.rb +131 -0
- data/lib/hobo/authentication_support.rb +15 -9
- data/lib/hobo/composite_model.rb +1 -1
- data/lib/hobo/controller.rb +7 -8
- data/lib/hobo/dryml.rb +9 -10
- data/lib/hobo/dryml/dryml_builder.rb +7 -1
- data/lib/hobo/dryml/dryml_doc.rb +161 -0
- data/lib/hobo/dryml/dryml_generator.rb +18 -9
- data/lib/hobo/dryml/part_context.rb +76 -42
- data/lib/hobo/dryml/tag_parameters.rb +1 -0
- data/lib/hobo/dryml/taglib.rb +2 -1
- data/lib/hobo/dryml/template.rb +39 -29
- data/lib/hobo/dryml/template_environment.rb +79 -37
- data/lib/hobo/dryml/template_handler.rb +66 -21
- data/lib/hobo/guest.rb +2 -10
- data/lib/hobo/hobo_helper.rb +125 -53
- data/lib/hobo/include_in_save.rb +0 -1
- data/lib/hobo/lifecycles.rb +54 -24
- data/lib/hobo/lifecycles/actions.rb +95 -31
- data/lib/hobo/lifecycles/creator.rb +18 -23
- data/lib/hobo/lifecycles/lifecycle.rb +86 -62
- data/lib/hobo/lifecycles/state.rb +1 -2
- data/lib/hobo/lifecycles/transition.rb +22 -28
- data/lib/hobo/model.rb +64 -176
- data/lib/hobo/model_controller.rb +67 -54
- data/lib/hobo/model_router.rb +5 -2
- data/lib/hobo/permissions.rb +397 -0
- data/lib/hobo/permissions/associations.rb +167 -0
- data/lib/hobo/scopes.rb +15 -38
- data/lib/hobo/scopes/association_proxy_extensions.rb +15 -5
- data/lib/hobo/scopes/automatic_scopes.rb +43 -18
- data/lib/hobo/scopes/named_scope_extensions.rb +2 -2
- data/lib/hobo/user.rb +10 -4
- data/lib/hobo/user_controller.rb +6 -5
- data/lib/hobo/view_hints.rb +58 -0
- data/rails_generators/hobo/hobo_generator.rb +7 -3
- data/rails_generators/hobo/templates/guest.rb +1 -13
- data/rails_generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
- data/rails_generators/hobo_model/hobo_model_generator.rb +4 -2
- data/rails_generators/hobo_model/templates/hints.rb +4 -0
- data/rails_generators/hobo_model/templates/model.rb +8 -8
- data/rails_generators/hobo_model_controller/hobo_model_controller_generator.rb +10 -0
- data/rails_generators/hobo_model_controller/templates/controller.rb +1 -1
- data/rails_generators/hobo_rapid/templates/hobo-rapid.js +91 -56
- data/rails_generators/hobo_rapid/templates/lowpro.js +15 -15
- data/rails_generators/hobo_rapid/templates/reset.css +36 -3
- data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +13 -17
- data/rails_generators/hobo_user_controller/templates/controller.rb +1 -1
- data/rails_generators/hobo_user_model/templates/model.rb +18 -16
- data/taglibs/core.dryml +60 -18
- data/taglibs/rapid.dryml +8 -401
- data/taglibs/rapid_core.dryml +586 -0
- data/taglibs/rapid_document_tags.dryml +28 -10
- data/taglibs/rapid_editing.dryml +92 -55
- data/taglibs/rapid_forms.dryml +406 -87
- data/taglibs/rapid_generics.dryml +1 -1
- data/taglibs/rapid_navigation.dryml +2 -1
- data/taglibs/rapid_pages.dryml +7 -16
- data/taglibs/rapid_plus.dryml +39 -14
- data/taglibs/rapid_support.dryml +1 -1
- data/taglibs/rapid_user_pages.dryml +14 -4
- data/tasks/{generate_tag_reference.rb → generate_tag_reference.rake} +49 -18
- data/tasks/hobo_tasks.rake +16 -0
- data/test/permissions/models/models.rb +134 -0
- data/test/permissions/models/schema.rb +55 -0
- data/test/permissions/models/test.sqlite3 +0 -0
- data/test/permissions/test_permissions.rb +436 -0
- metadata +27 -14
- data/lib/hobo/mass_assignment.rb +0 -64
- data/rails_generators/hobo/templates/patch_routing.rb +0 -30
- data/uninstall.rb +0 -1
@@ -0,0 +1,586 @@
|
|
1
|
+
<!-- Core Rapid tags and tags that don't belong anywhere else. -->
|
2
|
+
|
3
|
+
<!-- Renders a table with one row per field, where each row contains a `<th>` with the field name, and a `<td>` with (by default)
|
4
|
+
a `<view>` of the field.
|
5
|
+
|
6
|
+
### Attributes
|
7
|
+
|
8
|
+
- fields: Comma separated list of field names to display. Defaults to the fields returned by the `standard_fields` helper. That is, all fields apart from IDs and timestamps.
|
9
|
+
|
10
|
+
- force-all: All non-viewable fields will be skipped unless this attribute is given
|
11
|
+
|
12
|
+
- skip: Comma separated list of fields to exclude
|
13
|
+
|
14
|
+
- tag: The name of a tag to use inside the `<td>` to display the value. Defaults to `view`
|
15
|
+
|
16
|
+
- show-non-editable: By default, if `tag` is set to `input`, fields for which the current user does not have edit permission
|
17
|
+
will be skipped (the entire row is skipped). Set this attribute to keep them. (Note that `<input>` automatically degrades
|
18
|
+
to `<view>` if the user does not have edit permission.)
|
19
|
+
|
20
|
+
-->
|
21
|
+
<def tag="field-list" attrs="tag, no-edit">
|
22
|
+
<% tag ||= scope.in_form ? "input" : "view"; no_edit ||= "skip" %>
|
23
|
+
<labelled-item-list merge-attrs="&attributes - attrs_for(:with_fields)">
|
24
|
+
<with-fields merge-attrs="&attributes & attrs_for(:with_fields)">
|
25
|
+
<% field_name = this_field_name
|
26
|
+
input_attrs = {:no_edit => no_edit} if tag == "input" && no_edit == "disable"
|
27
|
+
-%>
|
28
|
+
<labelled-item unless="&tag == 'input' && no_edit == 'skip' && !can_edit?">
|
29
|
+
<item-label param="#{this_field.to_s.sub('?', '')}-label" unless="&field_name.blank?">
|
30
|
+
<do param="label"><%= field_name %></do>
|
31
|
+
</item-label>
|
32
|
+
<item-value param="#{this_field.to_s.sub('?', '')}-view" colspan="&2 if field_name.blank?">
|
33
|
+
<do param="view"><call-tag tag="&tag" param="#{this_field.to_s.sub('?', '')}-tag" merge-attrs="&input_attrs"/></do>
|
34
|
+
<div param="input-help" if="&tag.to_sym == :input && !this_field_help.blank?"><%= this_field_help %></div>
|
35
|
+
</item-value>
|
36
|
+
</labelled-item>
|
37
|
+
</with-fields>
|
38
|
+
</labelled-item-list>
|
39
|
+
</def>
|
40
|
+
|
41
|
+
<!-- Used to render nil values. By default renders "(Not Available)"
|
42
|
+
|
43
|
+
### Usage
|
44
|
+
|
45
|
+
Redefine in your app to have nil values displayed differently, e.g.:
|
46
|
+
|
47
|
+
<def tag="nil-view">-</def>
|
48
|
+
|
49
|
+
-->
|
50
|
+
<def tag="nil-view"><%= scope.nil_view || "(Not Available)" %></def>
|
51
|
+
|
52
|
+
<!--
|
53
|
+
`<table>` is extended in Rapid to provide a shorthand way to output a set of fields for a given collection. This is enabled using the `field` attribute (without the `field` attribute this is just the regular HTML `<table>` tag)
|
54
|
+
|
55
|
+
### Usage
|
56
|
+
|
57
|
+
If the context is an array of blog posts...
|
58
|
+
|
59
|
+
<table fields="name, created_at, description"/>
|
60
|
+
|
61
|
+
This will output a header row containing "Name", "Created At" and "Description" followed by a row for each record in the collection. By default, the `<view/>` tag is called for each field in the row. This can be altered with the `field-tag` attribute, e.g.
|
62
|
+
|
63
|
+
<table fields="name, created_at, description" field-tag="input"/>
|
64
|
+
|
65
|
+
This will use `<input/>` as the tag in each table cell instead of `<view/>`
|
66
|
+
|
67
|
+
### Additional Notes
|
68
|
+
|
69
|
+
* `<table>` provides parameters based on the names of the fields which can be used to further customise the output. For each field a heading parameter is provided, e.g. name-heading, created-at-heading, description-heading. These can be used to customise the headings:
|
70
|
+
|
71
|
+
<table fields="name, created_at, description">
|
72
|
+
<created-at-heading:>Creation Date</created-at-heading:>
|
73
|
+
</table>
|
74
|
+
* Similarly, "view" parameters are provided as an additional way to customise the table cells of the table body, e.g. `name-view`, `created-at-view`, `description-view`:
|
75
|
+
|
76
|
+
<table fields="name, created_at, description">
|
77
|
+
<created-at-view:><view format="%d %B %Y"/></created-at-view:>
|
78
|
+
</table>
|
79
|
+
* By adding an empty `control` parameter, the default control column is enable adding an edit link and delete button for each table row:
|
80
|
+
|
81
|
+
<table fields="name, created_at, description">
|
82
|
+
<controls:/>
|
83
|
+
</table>
|
84
|
+
The controls can be further customised using the "edit-link" and "delete-button" parameters or by providing completely new content for the control column, e.g.
|
85
|
+
|
86
|
+
<table fields="name, created_at, description">
|
87
|
+
<controls:>my controls!</controls:>
|
88
|
+
</table>
|
89
|
+
-->
|
90
|
+
<def tag="table" attrs="fields, field-tag, empty">
|
91
|
+
<if test="&!(fields || all_parameters.tr?)">
|
92
|
+
<%= element("table", attributes, all_parameters.default) %>
|
93
|
+
</if>
|
94
|
+
<else>
|
95
|
+
<% field_tag ||= "view" %>
|
96
|
+
<unless test="&this.empty? && !empty">
|
97
|
+
<% element "table", attributes - attrs_for(:with_fields) do %>
|
98
|
+
<thead if="&all_parameters[:thead] || fields" param>
|
99
|
+
<tr param="field-heading-row">
|
100
|
+
<with-field-names merge-attrs="&all_attributes & attrs_for(:with_fields)">
|
101
|
+
<th param="#{scope.field_name}-heading"><%= this.member_class.view_hints.field_name(scope.field_name) %></th>
|
102
|
+
</with-field-names>
|
103
|
+
<th if="&all_parameters[:controls]" class="controls"/>
|
104
|
+
</tr>
|
105
|
+
</thead>
|
106
|
+
<tbody>
|
107
|
+
<repeat>
|
108
|
+
<tr param if="&can_view?"
|
109
|
+
class="#{scope.even_odd} #{this_type.name.underscore} #{model_id_class}">
|
110
|
+
<if test="&fields">
|
111
|
+
<with-fields merge-attrs="&all_attributes & attrs_for(:with_fields)" force-all>
|
112
|
+
<td param="#{this_field.to_s.sub('?', '').gsub('.', '-')}-view"><call-tag tag="&field_tag"/></td>
|
113
|
+
</with-fields>
|
114
|
+
<td class="controls" param="controls" if="&all_parameters[:controls]">
|
115
|
+
<a param="edit-link" action="edit">Edit</a>
|
116
|
+
<delete-button param/>
|
117
|
+
</td>
|
118
|
+
</if>
|
119
|
+
</tr>
|
120
|
+
</repeat>
|
121
|
+
</tbody>
|
122
|
+
<tfoot if="&all_parameters[:tfoot]" param/>
|
123
|
+
<% end %>
|
124
|
+
</unless>
|
125
|
+
</else>
|
126
|
+
</def>
|
127
|
+
|
128
|
+
<!--
|
129
|
+
Provides a short hand way of displaying images in public/images
|
130
|
+
|
131
|
+
### Usage
|
132
|
+
|
133
|
+
<image src="hobo.png"/> -> <img src="/images/hobo.png"/>
|
134
|
+
<image src="blog/funny.jpg" alt="Funny Scene"/> -> <img src="/images/blog/funny.jpg" alt="Funny Scene"/>
|
135
|
+
-->
|
136
|
+
<def tag="image" attrs="src">
|
137
|
+
<img src="#{base_url}/images/#{src}" merge-attrs/>
|
138
|
+
</def>
|
139
|
+
|
140
|
+
|
141
|
+
<!-- Renders an ajax-progress 'spinner' using `spinner.gif` from the current theme, with a `class='hidden'` -->
|
142
|
+
<def tag="spinner">
|
143
|
+
<img src="#{base_url}/hobothemes/#{Hobo.current_theme}/images/spinner.gif" class="hidden" merge-attrs/>
|
144
|
+
</def>
|
145
|
+
|
146
|
+
|
147
|
+
<!-- Renders some standard JavaScript code that various features of the Rapid library rely on. This tag would typicallu be called from your `<page>` tag. The default Rapid pages include this already. -->
|
148
|
+
<def tag="hobo-rapid-javascripts"><%=
|
149
|
+
res = 'var hoboParts = {};'
|
150
|
+
# FIXME: This should interrogate the model-router - not the models
|
151
|
+
unless Hobo::Model.all_models.empty?
|
152
|
+
# Tell JS code how to pluralize names, unless they follow the simple rule
|
153
|
+
names = Hobo::Model.all_models.map do |m|
|
154
|
+
m = m.name.underscore
|
155
|
+
"#{m}: '#{m.pluralize}'" unless m.pluralize == m + 's'
|
156
|
+
end.compact
|
157
|
+
res << "var pluralisations = {#{names * ', '}}; "
|
158
|
+
end
|
159
|
+
base = [base_url, subsite].compact.join("/")
|
160
|
+
res << "urlBase = '#{base}'; hoboPagePath = '#{view_name}'"
|
161
|
+
if protect_against_forgery?
|
162
|
+
res << "; formAuthToken = { name: '#{request_forgery_protection_token}', value: '#{form_authenticity_token}' }"
|
163
|
+
end
|
164
|
+
res
|
165
|
+
%></def>
|
166
|
+
|
167
|
+
<!-- Renders the name of the current context using a variety of methods.
|
168
|
+
|
169
|
+
### Details
|
170
|
+
|
171
|
+
- Equivalent to `<nil-view>` if `this` is nil
|
172
|
+
- Equivalent to `<count>` if `this` is an Array
|
173
|
+
- Equivalent to `<type-name>` if `this` is a class
|
174
|
+
- If the context has a `name_attribute` defined, equivalent to `<view:abc/>` (where `abc` is the name attribute)
|
175
|
+
- Finally falls back to `this.to_s` (html escaped), but only if the user has view permission for `this`
|
176
|
+
|
177
|
+
### Attributes
|
178
|
+
|
179
|
+
- if-present: if given, nothing at all will be rendered for nil values (as opposed to rendering `<nil-view>`)
|
180
|
+
|
181
|
+
-->
|
182
|
+
<def tag="name" attrs="if-present"><%=
|
183
|
+
if this.nil?
|
184
|
+
nil_view unless if_present
|
185
|
+
else
|
186
|
+
if this.is_a?(Array)
|
187
|
+
count
|
188
|
+
elsif this.is_a?(Class)
|
189
|
+
type_name(attributes)
|
190
|
+
elsif (name_attr = this.class.try.name_attribute) && can_view?(this, name_attr)
|
191
|
+
view(merge_attrs(attributes, {:field => name_attr}))
|
192
|
+
elsif can_view?(this)
|
193
|
+
h this.to_s
|
194
|
+
end
|
195
|
+
end
|
196
|
+
%></def>
|
197
|
+
|
198
|
+
|
199
|
+
<!-- Renders a human readable version of the type of the context
|
200
|
+
|
201
|
+
### Details
|
202
|
+
|
203
|
+
- If `this` is already a class, the name of that class is used
|
204
|
+
- Otherwise, first `this.member_class` (for collections), then `this.class` are tried
|
205
|
+
- By default the name is titleised and singular.
|
206
|
+
|
207
|
+
### Attributes
|
208
|
+
|
209
|
+
- plural: pluralise the name
|
210
|
+
- lowercase: render the name in all lower case
|
211
|
+
- dasherize: render the name in lower case with dashes instead of spaces.
|
212
|
+
|
213
|
+
-->
|
214
|
+
<def tag="type-name" attrs="plural, lowercase, dasherize"><%=
|
215
|
+
type ||= (this if this.is_a?(Class)) || this.try.member_class || this.class
|
216
|
+
|
217
|
+
name = dasherize ? type.name.underscore.dasherize : type.name.titleize
|
218
|
+
name = name.pluralize if plural
|
219
|
+
name = name.downcase if lowercase
|
220
|
+
name
|
221
|
+
%></def>
|
222
|
+
|
223
|
+
|
224
|
+
<!-- Renders a human readable name of a collection
|
225
|
+
|
226
|
+
### Details
|
227
|
+
|
228
|
+
- Uses `this.origin_attribute` as the name.
|
229
|
+
- Falls back to `<type-name>` otherwise.
|
230
|
+
- By default the name is titleised and plural.
|
231
|
+
|
232
|
+
### Attributes
|
233
|
+
|
234
|
+
- singular: singularise the name
|
235
|
+
- lowercase: render the name in all lower case
|
236
|
+
- dasherize: render the name in lower case with dashes instead of spaces.
|
237
|
+
|
238
|
+
-->
|
239
|
+
<def tag="collection-name" attrs="singular, lowercase, dasherize"><%=
|
240
|
+
if (attr = this.try.origin_attribute)
|
241
|
+
name = attr.to_s
|
242
|
+
name = dasherize ? name.underscore.dasherize : name.titleize
|
243
|
+
name = name.singularize if singular
|
244
|
+
name = name.downcase if lowercase
|
245
|
+
name
|
246
|
+
else
|
247
|
+
type_name(:plural => !singular, :lowercase => lowercase, :dasherize => dasherize)
|
248
|
+
end
|
249
|
+
%></def>
|
250
|
+
|
251
|
+
<!--
|
252
|
+
`<a>` is extended in Rapid to automatically provide URLs for Hobo model routes
|
253
|
+
|
254
|
+
### Usage
|
255
|
+
|
256
|
+
The tag behaves as a regular HTML link or anchor if either the href or name attribute is given:
|
257
|
+
|
258
|
+
<a href="/admin">Admin</a> -> Output is exactly as provided, untouched by Rapid
|
259
|
+
|
260
|
+
If no href or name is given then the _context_ is used to determine the link URL.
|
261
|
+
The helper method `object_url` is used to construct the URL using restful routing:
|
262
|
+
|
263
|
+
If the context is a class then the link will be an index page:
|
264
|
+
|
265
|
+
<a with="&BlogPost">My Blog</a> -> <a href="/blog_posts">My Blog</a>
|
266
|
+
|
267
|
+
If the context is a hobo model instance then the link will be a show page:
|
268
|
+
|
269
|
+
<% blog_post = BlogPost.find(1) %>
|
270
|
+
<a with="&blog_post">My Blog Post</a> -> <a href="/blog_posts/1">My Blog Post</a>
|
271
|
+
|
272
|
+
An action can be provided for an alternative show page:
|
273
|
+
|
274
|
+
<a with="&blog_post" action="edit">Edit Post</a> -> <a href="/blog_posts/1/edit">Edit Post</a>
|
275
|
+
|
276
|
+
Or a new page if the context is a class:
|
277
|
+
|
278
|
+
<a with="&BlogPost" action="new">New Blog Post</a> -> <a href="/blog_posts/new">New Blog Post</a>
|
279
|
+
|
280
|
+
### Additional Features
|
281
|
+
|
282
|
+
* If the constructed route does not exist then the link will not be created, but the content of the link will still be output. E.g. when `/blog_posts` does not exist (because the hobo model controller does not exist or the index action is disabled):
|
283
|
+
|
284
|
+
<a with="&BlogPost">My Blog</a> -> My Blog
|
285
|
+
|
286
|
+
when the show action `/blog_posts/:id` does not exist:
|
287
|
+
|
288
|
+
<a with="&blog_post">My Blog Post</a> -> My Blog Post
|
289
|
+
* If no content text is provided then `<a>` will use the name method on the context to provide the text. E.g.
|
290
|
+
|
291
|
+
<a with="&blog_post"/> -> <a href="/blog_posts/1">My First Blog Post</a>`
|
292
|
+
<a with="&BlogPost"/> -> <a href="/blog_posts">Blog Posts</a>`
|
293
|
+
* If `action="new"` then `<a>` will check that the current user has permission to create the object
|
294
|
+
* Several useful classes are added automatically to the output `<a>`.
|
295
|
+
-->
|
296
|
+
<def tag="a" attrs="action, to, params, query-params, href, format, subsite"><%=
|
297
|
+
content = parameters.default
|
298
|
+
|
299
|
+
params = self.query_params.merge(params || HashWithIndifferentAccess.new) if query_params
|
300
|
+
|
301
|
+
if href || attributes[:name]
|
302
|
+
# Regular link
|
303
|
+
href += "?" + params.map { |n, v| "#{n}=#{v}" }.join('&') if !params.blank?
|
304
|
+
element(:a, attributes.update(:href => href), content)
|
305
|
+
else
|
306
|
+
target = to || this
|
307
|
+
|
308
|
+
if target.nil?
|
309
|
+
Hobo::Dryml.last_if = false
|
310
|
+
nil_view
|
311
|
+
elsif action == "new"
|
312
|
+
# Link to a new object form
|
313
|
+
new_record = target.new
|
314
|
+
new_record.set_creator(current_user)
|
315
|
+
href = object_url(target, "new", params._?.merge(:subsite => subsite))
|
316
|
+
|
317
|
+
if href && can_create?(new_record)
|
318
|
+
new_class_name = if target.respond_to?(:proxy_reflection)
|
319
|
+
target.proxy_reflection.klass.name
|
320
|
+
else
|
321
|
+
target.name
|
322
|
+
end
|
323
|
+
|
324
|
+
add_classes!(attributes, "new-#{new_class_name.underscore}-link")
|
325
|
+
content = "New #{new_class_name.titleize}" if content.blank?
|
326
|
+
Hobo::Dryml.last_if = true
|
327
|
+
element(:a, attributes.update(:href => href), content)
|
328
|
+
else
|
329
|
+
Hobo::Dryml.last_if = false
|
330
|
+
""
|
331
|
+
end
|
332
|
+
else
|
333
|
+
# Link to an existing object
|
334
|
+
|
335
|
+
content = name if content.blank?
|
336
|
+
|
337
|
+
href = object_url(target, action, (params || {}).merge(:subsite => subsite))
|
338
|
+
if href.nil?
|
339
|
+
# This target is registered with ModelRouter as not linkable
|
340
|
+
content
|
341
|
+
else
|
342
|
+
css_class = target.try.origin_attribute || target.class.name.underscore.dasherize
|
343
|
+
add_classes!(attributes, "#{css_class}-link")
|
344
|
+
|
345
|
+
href.sub!(/\?|$/, ".#{format}\\0") unless format.blank?
|
346
|
+
|
347
|
+
# Set default link text if none given
|
348
|
+
element(:a, attributes.update(:href => href), content)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
%></def>
|
353
|
+
|
354
|
+
<!--
|
355
|
+
Provides a read-only view tailored to the type of the object being viewed. `<view>` is a _polymorphic_ tag which means that there are a variety of definitions, each one written for a particular type. For example there are views for `Date`, `Time`, `Numeric`, `String` and `Boolean`. The type specific view is enclosed in a wrapper tag (typically a `<span>` or `<div>`) with some useful classes automatically added.
|
356
|
+
|
357
|
+
### Usage
|
358
|
+
|
359
|
+
Assuming the context is a blog post...
|
360
|
+
|
361
|
+
* Viewing a DateTime field:
|
362
|
+
|
363
|
+
<view:created_at/> -> <span class="view blog-post-created-at">June 09, 2008 15:36</span>
|
364
|
+
* Viewing a String field:
|
365
|
+
|
366
|
+
<view:title/> -> <span class="view blog-post-title">My First Blog Post</span>
|
367
|
+
* Viewing an Integer field:
|
368
|
+
|
369
|
+
<view:comment_count/> -> <span class="view blog-post-comment-count">4</span>
|
370
|
+
* Viewing the blog post itself results in a link to the blog post (using Rapid's `<a>` tag):
|
371
|
+
|
372
|
+
<view/> -> <span class="view model:blog-post-1"><a href="/blog_posts/1">My First Blog Post</a></span>
|
373
|
+
|
374
|
+
### Additional Notes
|
375
|
+
|
376
|
+
* The wrapper tag is `<span>` unless the field type is `Text` (different to `String`) where it is `<div>`. Use the `inline` or `block` attributes to force a `<span>` or a `<div>`, e.g.
|
377
|
+
|
378
|
+
<view:body/> -> <div class="view blog-post-body">This is my blog post body</div>
|
379
|
+
|
380
|
+
<view:body inline/> -> <span class="view blog-post-body">This is my blog post body</span>
|
381
|
+
|
382
|
+
<view:created_at block/> -> <div class="view blog-post-created-at">June 09, 2008 15:36</div>
|
383
|
+
* Use the `no-wrapper` attribute to remove the wrapper tag completely. e.g.
|
384
|
+
|
385
|
+
<view:created_at no-wrapper/> -> June 09, 2008 15:36
|
386
|
+
-->
|
387
|
+
<def tag="view" attrs="inline, block, if-blank, no-wrapper, truncate"><%=
|
388
|
+
raise HoboError, "view of non-viewable field '#{this_field}' of #{this_parent.typed_id rescue this_parent}" unless
|
389
|
+
can_view?
|
390
|
+
|
391
|
+
res = if this.nil? && if_blank.nil?
|
392
|
+
this_type.is_a?(Class) && this_type <= String ? "" : nil_view
|
393
|
+
elsif (refl = this_field_reflection) && refl.macro == :has_many
|
394
|
+
collection_view(attributes)
|
395
|
+
else
|
396
|
+
view_tag = find_polymorphic_tag("view")
|
397
|
+
|
398
|
+
if view_tag == "view" # i.e. it didn't find a type specific tag
|
399
|
+
if this.respond_to?(:to_html)
|
400
|
+
this.to_html(scope.xmldoctype)
|
401
|
+
else
|
402
|
+
this.to_s
|
403
|
+
end
|
404
|
+
else
|
405
|
+
attrs = add_classes(attributes, "view", type_and_field._?.dasherize, model_id_class)
|
406
|
+
|
407
|
+
view_attrs = attrs_for(view_tag)
|
408
|
+
the_view = send(view_tag, attrs & view_attrs)
|
409
|
+
|
410
|
+
the_view = if_blank if if_blank && the_view.blank?
|
411
|
+
|
412
|
+
truncate = 30 if truncate == true
|
413
|
+
the_view = self.truncate(the_view, truncate.to_i) if truncate
|
414
|
+
the_view = the_view.strip
|
415
|
+
|
416
|
+
if no_wrapper
|
417
|
+
the_view
|
418
|
+
else
|
419
|
+
wrapper = if inline
|
420
|
+
:span
|
421
|
+
elsif block || this.is_a?(HoboFields::Text)
|
422
|
+
:div
|
423
|
+
else
|
424
|
+
:span
|
425
|
+
end
|
426
|
+
element(wrapper, attrs - view_attrs, the_view)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
Hobo::Dryml.last_if = !res.blank?
|
431
|
+
res
|
432
|
+
%></def>
|
433
|
+
|
434
|
+
<!-- `<view>` calls this tag when called for a `has_many` collection. By default calls `<links-for-collection/>` -->
|
435
|
+
<def tag="collection-view" polymorphic><links-for-collection mergge-attrs/></def>
|
436
|
+
|
437
|
+
<!-- Renders a comma separated list of links (`<a>`), or "(none)" if the list is empty -->
|
438
|
+
<def tag="links-for-collection"><%= this.empty? ? "(none)" : context_map { a }.join(", ") %></def>
|
439
|
+
|
440
|
+
<!-- Renders `this.to_s(:long)`, or `this.strftime(format)` if the `format` attribute is given -->
|
441
|
+
<def tag="view" for="Date" attrs="format"><%= this && (format ? this.strftime(format) : this.to_s(:long)) %></def>
|
442
|
+
|
443
|
+
<!-- Renders `this.to_s(:long)`, or `this.strftime(format)` if the `format` attribute is given -->
|
444
|
+
<def tag="view" for="Time" attrs="format"><%= this && (format ? this.strftime(format) : this.to_s(:long)) %></def>
|
445
|
+
|
446
|
+
<!-- Renders `this.to_s(:long)`, or `this.strftime(format)` if the `format` attribute is given -->
|
447
|
+
<def tag="view" for="ActiveSupport::TimeWithZone" attrs="format"><%= this && (format ? this.strftime(format) : this.to_s(:long)) %></def>
|
448
|
+
|
449
|
+
<!-- Renders `this.to_s`, or `format % this` if the `format` attribute is given -->
|
450
|
+
<def tag="view" for="Numeric" attrs="format"><%= format ? format % this : this.to_s %></def>
|
451
|
+
|
452
|
+
<!-- Renders `this` with HTML escaping and newlines replaced with `<br>` tags -->
|
453
|
+
<def tag="view" for="string"><%=
|
454
|
+
if !(this.class == String) && this.respond_to?(:to_html) # workaround for Maruku which adds String#to_html : (
|
455
|
+
this.to_html(scope.xmldoctype)
|
456
|
+
else
|
457
|
+
h(this).gsub("\n", "<br#{scope.xmldoctype ? ' /' : ''}>")
|
458
|
+
end
|
459
|
+
%></def>
|
460
|
+
|
461
|
+
<!-- Renders 'Yes' for true and 'No' for false -->
|
462
|
+
<def tag="view" for="boolean"><%= this ? 'Yes' : 'No' %></def>
|
463
|
+
|
464
|
+
<!-- Renders a link (`<a>`) to `this` -->
|
465
|
+
<def tag="view" for="ActiveRecord::Base"><a merge-attrs/></def>
|
466
|
+
|
467
|
+
|
468
|
+
<!--
|
469
|
+
A convenience tag used to output a count and a correctly pluralised label. Works with any kind of collection such as an `ActiveRecord` association or an array.
|
470
|
+
|
471
|
+
### Usage
|
472
|
+
|
473
|
+
<count:comments/> -> <span class="count">1 Comment</span>
|
474
|
+
|
475
|
+
<count:viewings/> -> <span class="count">3 Viewings</span>
|
476
|
+
|
477
|
+
The label can be customised using the `label` attribute, e.g.
|
478
|
+
|
479
|
+
<count:comments label="blog post comment"/> -> <span class="count">12 blog post comments</span>
|
480
|
+
|
481
|
+
### Additional Notes
|
482
|
+
|
483
|
+
* Use the `prefix` attribute to insert words before the count. If the prefix is "are" or "is" then it will be pluralised if needed:
|
484
|
+
|
485
|
+
There <count:comments prefix="are"/> -> There <span class="count">is 1 Comment</span>
|
486
|
+
There <count:viewings prefix="are"/> -> There <span class="count">are 3 Viewings</span>
|
487
|
+
* Use the `lowercase` attribute to force the generated label to be lowercase:
|
488
|
+
|
489
|
+
<count:comments lowercase/> -> <span class="count">1 comment</span>
|
490
|
+
* Use the `if-any` attribute to output nothing if the count is zero. This can be followed by an `<else>` tag to handle the empty case:
|
491
|
+
|
492
|
+
<count:comments if-any/><else>There are no comments</else>
|
493
|
+
-->
|
494
|
+
<def tag="count" attrs="label, prefix, if-any, lowercase"><span class="count"><%=
|
495
|
+
raise Exception.new("asked for count of a string") if this.is_a?(String)
|
496
|
+
|
497
|
+
c = this.try.to_int || this.try.total_entries || (this.try.loaded? && this.try.length) || this.try.count || this.try.length
|
498
|
+
|
499
|
+
label ||= if this.is_a?(Class)
|
500
|
+
this.name
|
501
|
+
elsif (attr = this.try.origin_attribute)
|
502
|
+
attr.to_s.singularize
|
503
|
+
else
|
504
|
+
this.member_class.name
|
505
|
+
end.titleize
|
506
|
+
|
507
|
+
label = label.downcase if lowercase
|
508
|
+
|
509
|
+
Hobo::Dryml.last_if = c > 0 if if_any
|
510
|
+
if if_any && c == 0
|
511
|
+
""
|
512
|
+
else
|
513
|
+
main = label.blank? ? c : pluralize(c, label)
|
514
|
+
|
515
|
+
if prefix.in? %w(are is)
|
516
|
+
p = c == 1 ? "is" : "are"
|
517
|
+
p + ' ' + main.to_s
|
518
|
+
else
|
519
|
+
main
|
520
|
+
end
|
521
|
+
end
|
522
|
+
%></span></def>
|
523
|
+
|
524
|
+
|
525
|
+
<!-- Renders a `<link rel="Stylesheet" type="text/css">` to include the default stylesheet for the selected theme (select with `<set-theme>`). Included in the default pages.
|
526
|
+
-->
|
527
|
+
<def tag="theme-stylesheet" attrs="name">
|
528
|
+
<% name ||= Hobo.current_theme -%>
|
529
|
+
<link href="#{base_url}/hobothemes/#{Hobo.current_theme}/stylesheets/#{name}.css"
|
530
|
+
media="screen" rel="Stylesheet" type="text/css" />
|
531
|
+
</def>
|
532
|
+
|
533
|
+
<!-- Convenience tag to help with the common situation where you need to address the current user as "you", and refer to other users by name
|
534
|
+
|
535
|
+
### Usage
|
536
|
+
|
537
|
+
The context should be a user object. If `this == current_user` the "you" form is rendered, otherwise the form with the user's name:
|
538
|
+
|
539
|
+
- `<you have/> new mail` -> "you have new mail" or "Jim has new mail"
|
540
|
+
- `<you are/> now an admin` -> "you are now an admin" or "Jim is now an admin"
|
541
|
+
- `<you do/>n't want to go there` -> "you don't want to go there" or "Jim doesn't want to go there"
|
542
|
+
|
543
|
+
### Attributes
|
544
|
+
|
545
|
+
- titleize: render "You" instead of "you"
|
546
|
+
|
547
|
+
-->
|
548
|
+
<def tag="you" attrs="have, are, do, titleize"><if test="&this == current_user"><%= "#{titleize ? 'Y' : 'y'}ou#{' have' if have}#{' are' if are}#{' do' if do_}" %></if><else><do param="default"><name/><%= "#{' has' if have}#{' is' if are}#{' does' if do_}" %></do></else></def>
|
549
|
+
|
550
|
+
<!-- Equivalent to `<you titleize/>`. Yes it's an abuse of Ruby naming conventions, but it's so cute : ) -->
|
551
|
+
<def tag="You"><you merge titleize/></def>
|
552
|
+
|
553
|
+
<!-- Similar to `<you>`, but renders "Your" or "Fred's" -->
|
554
|
+
<def tag="your">
|
555
|
+
<if test="&this == current_user">your</if>
|
556
|
+
<else><do param="default"><%= n = name; n.ends_with?('s') ? "#{n}'" : "#{n}'s" %></do></else>
|
557
|
+
</def>
|
558
|
+
|
559
|
+
<!-- Capitalised versin of `<your>` -->
|
560
|
+
<def tag="Your">
|
561
|
+
<if test="&this == current_user">Your</if>
|
562
|
+
<else><do param="default"><%= n = name; n.ends_with?('s') ? "#{n}'" : "#{n}'s" %></do></else>
|
563
|
+
</def>
|
564
|
+
|
565
|
+
<!-- Renders "a book" or "an orange" according the the word passed in the attribute `word`
|
566
|
+
|
567
|
+
### Usage
|
568
|
+
|
569
|
+
To render either "Please select a recipe" or "Please select an event", according to the type of the `this`:
|
570
|
+
|
571
|
+
<a-or-an word="&type_name"/>
|
572
|
+
|
573
|
+
-->
|
574
|
+
<def tag="a-or-an" attrs="word"><%=
|
575
|
+
(word =~ /^[aeiou]/i ? "an " : "a ") + word
|
576
|
+
%></def>
|
577
|
+
|
578
|
+
|
579
|
+
<!-- Capitalised version of `<a-or-an>` -->
|
580
|
+
<def tag="A-or-An" attrs="word"><%=
|
581
|
+
(word =~ /^[aeiou]/i ? "An " : "A ") + word
|
582
|
+
%></def>
|
583
|
+
|
584
|
+
|
585
|
+
<!-- Renders a collection of string joined with ", ", or some other string passed in the `join` attribute -->
|
586
|
+
<def tag="comma-list" attrs="join"><%= this.join(join || ", ") %></def>
|