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