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
@@ -2,7 +2,7 @@ LowPro = {};
|
|
2
2
|
LowPro.Version = '0.5';
|
3
3
|
LowPro.CompatibleWithPrototype = '1.6';
|
4
4
|
|
5
|
-
if (Prototype.Version.indexOf(LowPro.CompatibleWithPrototype) != 0 &&
|
5
|
+
if (Prototype.Version.indexOf(LowPro.CompatibleWithPrototype) != 0 && console && console.warn)
|
6
6
|
console.warn("This version of Low Pro is tested with Prototype " + LowPro.CompatibleWithPrototype +
|
7
7
|
" it may not work as expected with this version (" + Prototype.Version + ")");
|
8
8
|
|
@@ -15,19 +15,19 @@ DOM = {};
|
|
15
15
|
// DOMBuilder for prototype
|
16
16
|
DOM.Builder = {
|
17
17
|
tagFunc : function(tag) {
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
18
|
+
return function() {
|
19
|
+
var attrs, children;
|
20
|
+
if (arguments.length>0) {
|
21
|
+
if (arguments[0].nodeName ||
|
22
|
+
typeof arguments[0] == "string")
|
23
|
+
children = arguments;
|
24
|
+
else {
|
25
|
+
attrs = arguments[0];
|
26
|
+
children = Array.prototype.slice.call(arguments, 1);
|
27
|
+
};
|
28
|
+
}
|
29
|
+
return DOM.Builder.create(tag, attrs, children);
|
30
|
+
};
|
31
31
|
},
|
32
32
|
create : function(tag, attrs, children) {
|
33
33
|
attrs = attrs || {}; children = children || []; tag = tag.toLowerCase();
|
@@ -119,7 +119,7 @@ Object.extend(Event.addBehavior, {
|
|
119
119
|
var observer = rules[selector];
|
120
120
|
var sels = selector.split(',');
|
121
121
|
sels.each(function(sel) {
|
122
|
-
var
|
122
|
+
var match = sel.match(/^([^:]*)(?::(.*)$)?/), css = match[1], event = match[2];
|
123
123
|
$$(css).each(function(element) {
|
124
124
|
if (event) {
|
125
125
|
observer = Event.addBehavior._wrapObserver(observer);
|
@@ -3,9 +3,9 @@
|
|
3
3
|
|
4
4
|
html, body, div, span, applet, object, iframe,
|
5
5
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
6
|
-
a, abbr, acronym, address, big, cite,
|
7
|
-
del, dfn,
|
8
|
-
small, strike,
|
6
|
+
a, abbr, acronym, address, big, cite,
|
7
|
+
del, dfn, font, img, ins, kbd, q, s, samp,
|
8
|
+
small, strike, sub, sup, tt, var,
|
9
9
|
dl, dt, dd, ol, ul, li,
|
10
10
|
fieldset, form, label, legend,
|
11
11
|
table, caption, tbody, tfoot, thead, tr, th, td {
|
@@ -20,6 +20,39 @@ table, caption, tbody, tfoot, thead, tr, th, td {
|
|
20
20
|
vertical-align: baseline;
|
21
21
|
}
|
22
22
|
|
23
|
+
code {
|
24
|
+
margin: 0;
|
25
|
+
padding: 0;
|
26
|
+
border: 0;
|
27
|
+
outline: 0;
|
28
|
+
font-weight: inherit;
|
29
|
+
font-style: inherit;
|
30
|
+
font-size: 100%;
|
31
|
+
vertical-align: baseline;
|
32
|
+
}
|
33
|
+
|
34
|
+
em {
|
35
|
+
margin: 0;
|
36
|
+
padding: 0;
|
37
|
+
border: 0;
|
38
|
+
outline: 0;
|
39
|
+
font-weight: inherit;
|
40
|
+
font-size: 100%;
|
41
|
+
font-family: inherit;
|
42
|
+
vertical-align: baseline;
|
43
|
+
}
|
44
|
+
|
45
|
+
strong {
|
46
|
+
margin: 0;
|
47
|
+
padding: 0;
|
48
|
+
border: 0;
|
49
|
+
outline: 0;
|
50
|
+
font-style: inherit;
|
51
|
+
font-size: 100%;
|
52
|
+
font-family: inherit;
|
53
|
+
vertical-align: baseline;
|
54
|
+
}
|
55
|
+
|
23
56
|
/* Remember to define focus styles! */
|
24
57
|
:focus {
|
25
58
|
outline: 0;
|
@@ -12,8 +12,9 @@
|
|
12
12
|
html, body {color: #193440; background: #193440;}
|
13
13
|
.page-header {color: white; background: #3F606E;}
|
14
14
|
.page-header .navigation.main-nav a {background: #5B8BA0;}
|
15
|
+
.page-header .navigation.main-nav li.current a {background: #FCFFF4; color: #222;}
|
15
16
|
.page-header .navigation.main-nav a:hover {background: #193440;}
|
16
|
-
.content {background: #FCFFF5;}
|
17
|
+
.section.content {background: #FCFFF5;}
|
17
18
|
.button {color: white; background: #5B8BA0;}
|
18
19
|
.button:hover {background-color: #193440;}
|
19
20
|
.add-to-collection {background: #E6E7DE;}
|
@@ -73,9 +74,11 @@ input.file_upload {
|
|
73
74
|
border: none;
|
74
75
|
width: auto;
|
75
76
|
font-size: 11px; font-weight: bold;
|
77
|
+
margin-top: 10px;
|
76
78
|
}
|
77
79
|
.button:hover {cursor: pointer;}
|
78
|
-
.actions {height: 100%; overflow: hidden; font-size: 11px;}
|
80
|
+
form .actions {height: 100%; overflow: hidden; font-size: 11px;}
|
81
|
+
form .actions input { margin: 0; }
|
79
82
|
|
80
83
|
.flash {
|
81
84
|
margin: 0 40px 10px; padding: 10px 30px; border-width: 2px 0;
|
@@ -103,14 +106,15 @@ input.file_upload {
|
|
103
106
|
#ajax-progress {
|
104
107
|
padding: 8px 20px 8px 40px;
|
105
108
|
border: 1px solid #444;
|
106
|
-
|
107
|
-
|
109
|
+
background: black url(../images/spinner.gif) no-repeat 10px 8px;
|
110
|
+
|
108
111
|
}
|
109
112
|
|
110
113
|
.article {margin: 20px 0; border-top: 1px dotted #ccc;}
|
111
114
|
|
112
|
-
.field-list th {width: 120px;}
|
115
|
+
.field-list th {width: 120px; white-space: nowrap;}
|
113
116
|
.field-list td {width: auto;}
|
117
|
+
.field-list .input-help { color: #888;}
|
114
118
|
|
115
119
|
.content-header, .content-body, .content-footer {margin: 0 45px 15px; padding: 0;}
|
116
120
|
.content-header {padding: 5px 0;}
|
@@ -184,7 +188,7 @@ input.file_upload {
|
|
184
188
|
.login-page, .forgot-password-page {width: 470px; margin-top: 40px;}
|
185
189
|
.login-page .content-header {padding-bottom: 0;}
|
186
190
|
.login-page .field-list {width: 370px;}
|
187
|
-
.login-page .actions {text-align: left; margin: 10px 0 10px 160px;}
|
191
|
+
.login-page form .actions {text-align: left; margin: 0; padding: 10px 0 10px 160px;}
|
188
192
|
.signup-page .field-list {width: 370px;}
|
189
193
|
.signup-page .content-body, .signup-page .content-header { margin-left: 120px; margin-right: 120px;}
|
190
194
|
.login-page .field-list td, .signup-page .field-list td {width: auto;}
|
@@ -244,7 +248,7 @@ div.ordering-handle { float: left; background: #ccc; color: white; margin-right:
|
|
244
248
|
.card.content.with-owner .content {
|
245
249
|
float: right; width: 72%;
|
246
250
|
}
|
247
|
-
ul.collection > li {
|
251
|
+
ul.collection > li { margin-left: 0; list-style: none;}
|
248
252
|
.empty-collection-message {margin-top: 20px;}
|
249
253
|
|
250
254
|
.new-link {margin-top: 20px;}
|
@@ -311,14 +315,6 @@ ul.input-many > li { overflow:hidden; zoom:1;}
|
|
311
315
|
ul.input-many .input-many-item {float:left;}
|
312
316
|
ul.input-many div.buttons {float:left; margin-left:10px;}
|
313
317
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
318
|
+
ul.check-many { list-style-type: none; margin-left: 0px;}
|
319
|
+
ul.check-many li input { vertical-align: -20%;}
|
324
320
|
|
@@ -3,8 +3,8 @@ class <%= class_name %> < ActiveRecord::Base
|
|
3
3
|
hobo_user_model # Don't put anything above this
|
4
4
|
|
5
5
|
fields do
|
6
|
-
|
7
|
-
email_address :email_address
|
6
|
+
name :string, :unique
|
7
|
+
email_address :email_address, :unique, :login => true
|
8
8
|
administrator :boolean, :default => false
|
9
9
|
timestamps
|
10
10
|
end
|
@@ -18,37 +18,39 @@ class <%= class_name %> < ActiveRecord::Base
|
|
18
18
|
|
19
19
|
lifecycle do
|
20
20
|
|
21
|
-
|
21
|
+
state :active, :default => true
|
22
22
|
|
23
|
-
create :
|
24
|
-
:params => [:
|
25
|
-
:become => :active
|
23
|
+
create :signup, :available_to => "Guest",
|
24
|
+
:params => [:name, :email_address, :password, :password_confirmation],
|
25
|
+
:become => :active
|
26
26
|
|
27
|
-
transition :
|
27
|
+
transition :request_password_reset, { :active => :active }, :new_key => true do
|
28
28
|
<%= class_name %>Mailer.deliver_forgot_password(self, lifecycle.key)
|
29
29
|
end
|
30
30
|
|
31
|
-
transition :
|
31
|
+
transition :reset_password, { :active => :active }, :available_to => :key_holder,
|
32
32
|
:update => [ :password, :password_confirmation ]
|
33
33
|
|
34
34
|
end
|
35
35
|
|
36
36
|
|
37
|
-
# ---
|
37
|
+
# --- Permissions --- #
|
38
38
|
|
39
|
-
def
|
40
|
-
|
39
|
+
def create_permitted?
|
40
|
+
false
|
41
41
|
end
|
42
42
|
|
43
|
-
def
|
44
|
-
|
43
|
+
def update_permitted?
|
44
|
+
acting_user.administrator? || (acting_user == self && only_changed?(:crypted_password, :email_address))
|
45
|
+
# Note: crypted_password has attr_protected so although it is permitted to change, it cannot be changed
|
46
|
+
# directly from a form submission.
|
45
47
|
end
|
46
48
|
|
47
|
-
def
|
48
|
-
|
49
|
+
def destroy_permitted?
|
50
|
+
acting_user.administrator?
|
49
51
|
end
|
50
52
|
|
51
|
-
def
|
53
|
+
def view_permitted?(field)
|
52
54
|
true
|
53
55
|
end
|
54
56
|
|
data/taglibs/core.dryml
CHANGED
@@ -1,41 +1,78 @@
|
|
1
|
+
<!-- Core DRYML tags. These are included implicitly and are always available. Contains mainly control-flow tags. -->
|
2
|
+
|
3
|
+
<!-- Call the tag given by the `tag` attribute. This lets you call tags dynamically based on some runtime value.
|
4
|
+
It's the DRYML equivalent of Ruby's `send` method.
|
5
|
+
-->
|
1
6
|
<def tag="call-tag" attrs="tag">
|
2
7
|
<%= send(tag.gsub('-', '_'), attributes, parameters) %>
|
3
8
|
</def>
|
4
9
|
|
5
10
|
|
11
|
+
<!-- Wrap the body in the tag specified by the `tag` attribute, iff `when` is true.
|
12
|
+
|
13
|
+
Using regular DRYML conditional logic it is rather akward to conditionally wrap some tag in another tag. This tag makes it easy to do that.
|
14
|
+
|
15
|
+
### Usage
|
16
|
+
|
17
|
+
For example, you might want to wrap an `<img>` tag in an `<a>` tag but only under certain conditions. Say the current context has an `href` attribute that may or may not be nil. We want to wrap the img in `<a>` if `href` is not nil:
|
18
|
+
|
19
|
+
<wrap when="&this.href.present?" tag="a" href="&this.href"><img src="&this.img_filename"/></wrap>
|
20
|
+
{: .dryml}
|
21
|
+
-->
|
6
22
|
<def tag="wrap" attrs="tag, when, parameter">
|
7
23
|
<% parameter ||= :default %>
|
8
24
|
<%= when_ ? send(tag, attributes, { parameter.to_sym => parameters[:default] }) : parameters.default %>
|
9
25
|
</def>
|
10
26
|
|
11
|
-
|
27
|
+
|
28
|
+
<!-- DRYML version of `render(:partial => 'my_partial')`
|
29
|
+
|
30
|
+
### Usage
|
31
|
+
|
32
|
+
<partial name="my-partial" locals="&{:x => 10, :y => 20}"/>
|
33
|
+
-->
|
12
34
|
<def tag="partial" attrs="name, locals"><%=
|
13
35
|
locals ||= {}
|
14
36
|
render(:partial => name, :locals => locals.merge(:this => this))
|
15
37
|
%></def>
|
16
38
|
|
17
39
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end.join(join)
|
31
|
-
end
|
32
|
-
end %></if></def>
|
40
|
+
<!-- Repeat a section of mark-up. The context should be a collection (anything that responds to `each`). The content of the call to `<repeat>` will be repeated for each item in the collection, and the context will be set to each item in turn.
|
41
|
+
|
42
|
+
### Attributes
|
43
|
+
|
44
|
+
- join: The value of this attribute, if given, will be inserted between each of the items (e.g. `join=", "` is very common).
|
45
|
+
-->
|
46
|
+
<def tag="repeat" attrs="join"><if><%=
|
47
|
+
raise ArgumentError, "Cannot <repeat> on #{this.inspect}" unless this.respond_to? :each
|
48
|
+
context_map do
|
49
|
+
parameters.default
|
50
|
+
end.join(join)
|
51
|
+
%></if></def>
|
33
52
|
|
34
53
|
|
54
|
+
<!-- The 'do nothing' tag. Used to add parameters or change context without adding any markup -->
|
35
55
|
<def tag="do"><%= parameters.default %></def>
|
56
|
+
|
57
|
+
<!-- Alias of `do` -->
|
36
58
|
<def tag="with" alias-of="do"/>
|
37
59
|
|
60
|
+
<!-- DRYML's 'if' test
|
61
|
+
|
62
|
+
### Usage
|
63
|
+
|
64
|
+
<if test="¤t_user.administrtator?">Logged in as administrator</if>
|
65
|
+
<else>Logged in as normal user</else>
|
66
|
+
|
67
|
+
**IMPORTANT NOTE**: `<if>` tests for non-blank vs. blank (as defined by ActiveSuport), not true vs. false.
|
68
|
+
|
69
|
+
If you do not give the `test` attribute, uses the current context instead. This allows a nice trick like this:
|
38
70
|
|
71
|
+
<if:comments>...</if>
|
72
|
+
|
73
|
+
This has the double effect of changing the context to the `this.comments`, and only evaluating the body if there are comments (because an empty
|
74
|
+
collection is considered blank)
|
75
|
+
-->
|
39
76
|
<def tag="if" attrs="test"><%=
|
40
77
|
test = all_attributes.fetch(:test, this)
|
41
78
|
res = (cond = !test.blank?) ? parameters.default : ""
|
@@ -43,10 +80,14 @@
|
|
43
80
|
res
|
44
81
|
%></def>
|
45
82
|
|
83
|
+
<!-- General purpose `else` clause.
|
46
84
|
|
85
|
+
`<else>` works with various tags such as `<if>` and `<repeat>` (the else clause will be output if the collection was empty). It simply outputs its content if `Hobo::Dryml.last_if` is false. This is pretty much a crazy hack which violates many good principles of language design, but it's very useful : )
|
86
|
+
-->
|
47
87
|
<def tag="else"><%= parameters.default unless Hobo::Dryml.last_if %></def>
|
48
88
|
|
49
89
|
|
90
|
+
<!-- Same behaviour as `<if>`, except the test is negated. -->
|
50
91
|
<def tag="unless" attrs="test"><%=
|
51
92
|
test = all_attributes.fetch(:test, this)
|
52
93
|
res = (cond = test.blank?) ? parameters.default : ""
|
@@ -55,8 +96,9 @@
|
|
55
96
|
%></def>
|
56
97
|
|
57
98
|
|
58
|
-
|
99
|
+
<!-- nodoc. -->
|
59
100
|
<def tag="fake-field-context" attrs="fake-field, context"><%=
|
60
101
|
res = ""
|
61
102
|
new_field_context(fake_field, context) { res << parameters.default }
|
62
|
-
res
|
103
|
+
res
|
104
|
+
%></def>
|
data/taglibs/rapid.dryml
CHANGED
@@ -1,5 +1,13 @@
|
|
1
|
+
<!--
|
2
|
+
|
3
|
+
The Rapid tag library makes web development go fast. The Rapid tag library is your friend.
|
4
|
+
|
5
|
+
(This taglib defines no tags - it just includes all the other taglibs. More along. Nothing to see here.)
|
6
|
+
-->
|
7
|
+
|
1
8
|
<include module="Hobo::RapidHelper"/>
|
2
9
|
|
10
|
+
<include src="rapid_core"/>
|
3
11
|
<include src="rapid_support"/>
|
4
12
|
<include src="rapid_document_tags"/>
|
5
13
|
<include src="rapid_pages"/>
|
@@ -9,404 +17,3 @@
|
|
9
17
|
<include src="rapid_plus"/>
|
10
18
|
<include src="rapid_generics"/>
|
11
19
|
<include src="rapid_lifecycles"/>
|
12
|
-
|
13
|
-
<def tag="field-list" attrs="tag">
|
14
|
-
<% tag ||= scope.in_form ? "input" : "view" %>
|
15
|
-
<labelled-item-list merge-attrs="&attributes - attrs_for(:with_fields)">
|
16
|
-
<with-fields merge-attrs="&attributes & attrs_for(:with_fields)">
|
17
|
-
<labelled-item unless="&tag == 'input' && !can_edit?">
|
18
|
-
<item-label param="#{this_field.to_s.sub('?', '')}-label">
|
19
|
-
<do param="label"><%= this_field.to_s.titleize %></do>
|
20
|
-
</item-label>
|
21
|
-
<item-value param="#{this_field.to_s.sub('?', '')}-view">
|
22
|
-
<do param="view"><call-tag tag="&tag" param="#{this_field.to_s.sub('?', '')}-tag"/></do>
|
23
|
-
</item-value>
|
24
|
-
</labelled-item>
|
25
|
-
</with-fields>
|
26
|
-
</labelled-item-list>
|
27
|
-
</def>
|
28
|
-
|
29
|
-
|
30
|
-
<def tag="item"><% scope.items << parameters.default %></def>
|
31
|
-
|
32
|
-
|
33
|
-
<def tag="nil-view"><%= scope.nil_view || "(Not Available)" %></def>
|
34
|
-
|
35
|
-
|
36
|
-
<def tag="table" attrs="fields, field-tag, empty">
|
37
|
-
<if test="&!(fields || all_parameters.tr?)">
|
38
|
-
<%= element("table", attributes, all_parameters.default) %>
|
39
|
-
</if>
|
40
|
-
<else>
|
41
|
-
<% field_tag ||= "view" %>
|
42
|
-
<unless test="&this.empty? && !empty">
|
43
|
-
<% element "table", attributes - attrs_for(:with_fields) do %>
|
44
|
-
<thead if="&all_parameters[:thead] || fields" param>
|
45
|
-
<tr param="field-heading-row">
|
46
|
-
<with-field-names merge-attrs="&all_attributes & attrs_for(:with_fields)">
|
47
|
-
<th param="#{scope.field_name}-heading"><%= scope.field_name.titleize %></th>
|
48
|
-
</with-field-names>
|
49
|
-
<th if="&all_parameters[:controls]" class="controls"/>
|
50
|
-
</tr>
|
51
|
-
</thead>
|
52
|
-
<tbody>
|
53
|
-
<repeat>
|
54
|
-
<tr param if="&can_view?"
|
55
|
-
class="#{scope.even_odd} #{this_type.name.underscore} #{model_id_class}">
|
56
|
-
<if test="&fields">
|
57
|
-
<with-fields merge-attrs="&all_attributes & attrs_for(:with_fields)" force-all>
|
58
|
-
<td param="#{this_field.to_s.sub('?', '').gsub('.', '-')}-view"><call-tag tag="&field_tag"/></td>
|
59
|
-
</with-fields>
|
60
|
-
<td class="controls" param="controls" if="&all_parameters[:controls]">
|
61
|
-
<a param="edit-link" action="edit">Edit</a>
|
62
|
-
<delete-button param/>
|
63
|
-
</td>
|
64
|
-
</if>
|
65
|
-
</tr>
|
66
|
-
</repeat>
|
67
|
-
</tbody>
|
68
|
-
<tfoot if="&all_parameters[:tfoot]" param/>
|
69
|
-
<% end %>
|
70
|
-
</unless>
|
71
|
-
</else>
|
72
|
-
</def>
|
73
|
-
|
74
|
-
|
75
|
-
<def tag="image" attrs="src">
|
76
|
-
<img src="#{base_url}/images/#{src}" merge-attrs/>
|
77
|
-
</def>
|
78
|
-
|
79
|
-
|
80
|
-
<def tag="spinner">
|
81
|
-
<img src="#{base_url}/hobothemes/#{Hobo.current_theme}/images/spinner.gif" class="hidden" merge-attrs/>
|
82
|
-
</def>
|
83
|
-
|
84
|
-
|
85
|
-
<def tag="hobo-rapid-javascripts"><%=
|
86
|
-
res = 'var hoboParts = {};'
|
87
|
-
# FIXME: This should interrogate the model-router - not the models
|
88
|
-
unless Hobo::Model.all_models.empty?
|
89
|
-
# Tell JS code how to pluralize names, unless they follow the simple rule
|
90
|
-
names = Hobo::Model.all_models.map do |m|
|
91
|
-
m = m.name.underscore
|
92
|
-
"#{m}: '#{m.pluralize}'" unless m.pluralize == m + 's'
|
93
|
-
end.compact
|
94
|
-
res << "var pluralisations = {#{names * ', '}}; "
|
95
|
-
end
|
96
|
-
base = [base_url, subsite].compact.join("/")
|
97
|
-
res << "urlBase = '#{base}'; hoboPagePath = '#{view_name}'"
|
98
|
-
if request_forgery_protection_token
|
99
|
-
res << "; formAuthToken = { name: '#{request_forgery_protection_token}', value: '#{form_authenticity_token}' }"
|
100
|
-
end
|
101
|
-
res
|
102
|
-
%></def>
|
103
|
-
|
104
|
-
|
105
|
-
<def tag="name" attrs="if-present, raw"><%=
|
106
|
-
if this.nil?
|
107
|
-
nil_view unless if_present
|
108
|
-
else
|
109
|
-
name_tag = find_polymorphic_tag("name")
|
110
|
-
if name_tag != "name"
|
111
|
-
send(name_tag, attributes)
|
112
|
-
elsif this.is_a?(Array) && this.respond_to?(:proxy_reflection)
|
113
|
-
count
|
114
|
-
elsif this.is_a? Class and this < ActiveRecord::Base
|
115
|
-
this.name.pluralize.titleize
|
116
|
-
elsif (name_attr = this.class.try.name_attribute) && can_view?(this, name_attr)
|
117
|
-
if raw
|
118
|
-
this.send(name_attr)
|
119
|
-
else
|
120
|
-
view(merge_attrs(attributes, {:field => name_attr}))
|
121
|
-
end
|
122
|
-
elsif can_view?(this)
|
123
|
-
this.to_s
|
124
|
-
end
|
125
|
-
end
|
126
|
-
%></def>
|
127
|
-
|
128
|
-
|
129
|
-
<def tag="type-name" attrs="type, plural, lowercase, dasherize"><%=
|
130
|
-
type ||= (this if this.is_a?(Class)) || this.try.member_class || this.class
|
131
|
-
|
132
|
-
name = dasherize ? type.name.underscore.dasherize : type.name.titleize
|
133
|
-
name = name.pluralize if plural
|
134
|
-
name = name.downcase if lowercase
|
135
|
-
name
|
136
|
-
%></def>
|
137
|
-
|
138
|
-
|
139
|
-
<def tag="collection-name" attrs="singular, lowercase, dasherize"><%=
|
140
|
-
if (attr = this.try.origin_attribute)
|
141
|
-
name = attr.to_s
|
142
|
-
name = dasherize ? name.underscore.dasherize : name.titleize
|
143
|
-
name = name.singularize if singular
|
144
|
-
name = name.downcase if lowercase
|
145
|
-
name
|
146
|
-
else
|
147
|
-
type_name(:plural => !singular, :lowercase => lowercase, :dasherize => dasherize)
|
148
|
-
end
|
149
|
-
%></def>
|
150
|
-
|
151
|
-
|
152
|
-
<def tag="a" attrs="action, to, params, query-params, href, format, subsite"><%=
|
153
|
-
content = parameters.default
|
154
|
-
|
155
|
-
params = self.query_params.merge(params || HashWithIndifferentAccess.new) if query_params
|
156
|
-
|
157
|
-
if href || attributes[:name]
|
158
|
-
# Regular link
|
159
|
-
href += "?" + params.map { |n, v| "#{n}=#{v}" }.join('&') if !params.blank?
|
160
|
-
element(:a, attributes.update(:href => href), content)
|
161
|
-
else
|
162
|
-
target = to || this
|
163
|
-
|
164
|
-
if target.nil?
|
165
|
-
Hobo::Dryml.last_if = false
|
166
|
-
nil_view
|
167
|
-
elsif action == "new"
|
168
|
-
# Link to a new object form
|
169
|
-
new_record = target.new
|
170
|
-
new_record.set_creator(current_user)
|
171
|
-
href = object_url(target, "new", params._?.merge(:subsite => subsite))
|
172
|
-
|
173
|
-
if href && can_create?(new_record)
|
174
|
-
new_class_name = if target.respond_to?(:proxy_reflection)
|
175
|
-
target.proxy_reflection.klass.name
|
176
|
-
else
|
177
|
-
target.name
|
178
|
-
end
|
179
|
-
|
180
|
-
add_classes!(attributes, "new-#{new_class_name.underscore}-link")
|
181
|
-
content = "New #{new_class_name.titleize}" if content.blank?
|
182
|
-
Hobo::Dryml.last_if = true
|
183
|
-
element(:a, attributes.update(:href => href), content)
|
184
|
-
else
|
185
|
-
Hobo::Dryml.last_if = false
|
186
|
-
""
|
187
|
-
end
|
188
|
-
else
|
189
|
-
# Link to an existing object
|
190
|
-
|
191
|
-
content = name if content.blank?
|
192
|
-
|
193
|
-
href = object_url(target, action, (params || {}).merge(:subsite => subsite))
|
194
|
-
if href.nil?
|
195
|
-
# This target is registered with ModelRouter as not linkable
|
196
|
-
content
|
197
|
-
else
|
198
|
-
css_class = target.try.origin_attribute || target.class.name.underscore.dasherize
|
199
|
-
add_classes!(attributes, "#{css_class}-link")
|
200
|
-
|
201
|
-
href.sub!(/\?|$/, ".#{format}\\0") unless format.blank?
|
202
|
-
|
203
|
-
# Set default link text if none given
|
204
|
-
element(:a, attributes.update(:href => href), content)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
%></def>
|
209
|
-
|
210
|
-
|
211
|
-
<def tag="view" attrs="inline, block, if-blank, no-wrapper, truncate"><%=
|
212
|
-
raise HoboError, "view of non-viewable field '#{this_field}' of #{this_parent.typed_id rescue this_parent}" unless
|
213
|
-
can_view?
|
214
|
-
|
215
|
-
res = if this.nil? && if_blank.nil?
|
216
|
-
this_type.is_a?(Class) && this_type <= String ? "" : nil_view
|
217
|
-
elsif (refl = this_field_reflection) && refl.macro == :has_many
|
218
|
-
has_many_view(attributes)
|
219
|
-
else
|
220
|
-
|
221
|
-
view_tag = find_polymorphic_tag("view")
|
222
|
-
|
223
|
-
if view_tag == "view" # i.e. it didn't find a type specific tag
|
224
|
-
if this.respond_to?(:to_html)
|
225
|
-
this.to_html(scope.xmldoctype)
|
226
|
-
else
|
227
|
-
this.to_s
|
228
|
-
end
|
229
|
-
else
|
230
|
-
attrs = add_classes(attributes, "view", type_and_field._?.dasherize, model_id_class)
|
231
|
-
|
232
|
-
view_attrs = attrs_for(view_tag)
|
233
|
-
the_view = send(view_tag, attrs & view_attrs)
|
234
|
-
|
235
|
-
the_view = if_blank if if_blank && the_view.blank?
|
236
|
-
|
237
|
-
truncate = 30 if truncate == true
|
238
|
-
the_view = self.truncate(the_view, truncate.to_i) if truncate
|
239
|
-
the_view = the_view.strip
|
240
|
-
|
241
|
-
if no_wrapper
|
242
|
-
the_view
|
243
|
-
else
|
244
|
-
wrapper = if inline
|
245
|
-
:span
|
246
|
-
elsif block || this.is_a?(HoboFields::Text)
|
247
|
-
:div
|
248
|
-
else
|
249
|
-
:span
|
250
|
-
end
|
251
|
-
element(wrapper, attrs - view_attrs, the_view)
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
Hobo::Dryml.last_if = !res.blank?
|
256
|
-
res
|
257
|
-
%></def>
|
258
|
-
|
259
|
-
|
260
|
-
<def tag="belongs-to-view"><a merge-attrs/></def>
|
261
|
-
|
262
|
-
<def tag="has-many-view"><%= this.empty? ? "(none)" : context_map { a }.join(", ") %></def>
|
263
|
-
|
264
|
-
<def tag="view" for="Date" attrs="format"><%= this && (format ? this.strftime(format) : this.to_s(:long)) %></def>
|
265
|
-
|
266
|
-
<def tag="view" for="Time" attrs="format"><%= this && (format ? this.strftime(format) : this.to_s(:long)) %></def>
|
267
|
-
|
268
|
-
<def tag="view" for="ActiveSupport::TimeWithZone" attrs="format"><%= this && (format ? this.strftime(format) : this.to_s(:long)) %></def>
|
269
|
-
|
270
|
-
<def tag="view" for="Numeric" attrs="format"><%= format ? format % this : this.to_s %></def>
|
271
|
-
|
272
|
-
<def tag="view" for="string"><%= this.try.to_html(scope.xmldoctype) || h(this).gsub("\n", "<br#{scope.xmldoctype ? ' /' : ''}>") %></def>
|
273
|
-
|
274
|
-
<def tag="view" for="boolean"><%= this ? 'Yes' : 'No' %></def>
|
275
|
-
|
276
|
-
<def tag="view" for="ActiveRecord::Base"><a/></def>
|
277
|
-
|
278
|
-
|
279
|
-
<def tag="count" attrs="label, prefix, if-any, lowercase"><span class="count"><%=
|
280
|
-
raise Exception.new("asked for count of a string") if this.is_a?(String)
|
281
|
-
|
282
|
-
c = this.try.to_int || this.try.total_entries || (this.try.loaded? && this.try.length) || this.try.count || this.try.length
|
283
|
-
|
284
|
-
label ||= if this.is_a?(Class)
|
285
|
-
this.name
|
286
|
-
elsif (attr = this.try.origin_attribute)
|
287
|
-
attr.to_s.singularize
|
288
|
-
else
|
289
|
-
this.member_class.name
|
290
|
-
end.titleize
|
291
|
-
|
292
|
-
label = label.downcase if lowercase
|
293
|
-
|
294
|
-
Hobo::Dryml.last_if = c > 0 if if_any
|
295
|
-
if if_any && c == 0
|
296
|
-
""
|
297
|
-
else
|
298
|
-
main = label.blank? ? c : pluralize(c, label)
|
299
|
-
|
300
|
-
if prefix.in? %w(are is)
|
301
|
-
p = c == 1 ? "is" : "are"
|
302
|
-
p + ' ' + main.to_s
|
303
|
-
else
|
304
|
-
main
|
305
|
-
end
|
306
|
-
end
|
307
|
-
%></span></def>
|
308
|
-
|
309
|
-
|
310
|
-
<def tag="theme-stylesheet" attrs="name">
|
311
|
-
<% name ||= Hobo.current_theme -%>
|
312
|
-
<link href="#{base_url}/hobothemes/#{Hobo.current_theme}/stylesheets/#{name}.css"
|
313
|
-
media="screen" rel="Stylesheet" type="text/css" />
|
314
|
-
</def>
|
315
|
-
|
316
|
-
|
317
|
-
<!-- The Tags defined below here are a bit rough and will be improved
|
318
|
-
in the future - use at your own risk. -->
|
319
|
-
|
320
|
-
<def tag="has-many-table" attrs="part-id, delete-buttons, headings, id">
|
321
|
-
<table-for headings="&headings" merge-attrs="&true">
|
322
|
-
|
323
|
-
<do param="default"/>
|
324
|
-
|
325
|
-
<if test="&delete_buttons != false && can_delete?(this)">
|
326
|
-
<td><delete-button/></td>
|
327
|
-
</if>
|
328
|
-
</table-for>
|
329
|
-
<else>
|
330
|
-
<p>No <%= this.member_class.name.titleize.pluralize.downcase %> to display</p>
|
331
|
-
</else>
|
332
|
-
<div>
|
333
|
-
<create-button update="&id || part_id"/>
|
334
|
-
</div>
|
335
|
-
</def>
|
336
|
-
|
337
|
-
|
338
|
-
<def tag="add-by-name" attrs="action-name, add-text, update, part-id">
|
339
|
-
<% add_to = this
|
340
|
-
refl = this_type
|
341
|
-
joins = this_parent.send(refl.through_reflection.name)
|
342
|
-
add_name = refl.klass.name.titleize
|
343
|
-
action_name ||= "Add #{add_name}"
|
344
|
-
add_text ||= "To #{action_name.downcase} #{a_or_an add_name.downcase}, " +
|
345
|
-
"enter its name"
|
346
|
-
source = refl.source_reflection.name
|
347
|
-
%>
|
348
|
-
<do param="default" with="&joins"/>
|
349
|
-
<with with="&joins.new_without_appending">
|
350
|
-
<if test="can_create?">
|
351
|
-
<form update="&[update, part_id]" message="&action_name" hidden-fields="*">
|
352
|
-
<p>
|
353
|
-
<%= add_text %>:
|
354
|
-
<belongs-to-autocompleting-field field="&source" where-not-in="&dom_id(add_to)" class="autosubmit"/>
|
355
|
-
</p>
|
356
|
-
</form>
|
357
|
-
</if>
|
358
|
-
</with>
|
359
|
-
</def>
|
360
|
-
|
361
|
-
|
362
|
-
<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>
|
363
|
-
|
364
|
-
<def tag="You"><you merge titleize/></def>
|
365
|
-
|
366
|
-
<def tag="your">
|
367
|
-
<if test="&this == current_user">your</if>
|
368
|
-
<else><do param="default"><%= n = name; n.ends_with?('s') ? "#{n}'" : "#{n}'s" %></do></else>
|
369
|
-
</def>
|
370
|
-
|
371
|
-
|
372
|
-
<def tag="Your">
|
373
|
-
<if test="&this == current_user">Your</if>
|
374
|
-
<else><do param="default"><%= n = name; n.ends_with?('s') ? "#{n}'" : "#{n}'s" %></do></else>
|
375
|
-
</def>
|
376
|
-
|
377
|
-
|
378
|
-
<def tag="live-search">
|
379
|
-
<div class="search">
|
380
|
-
<label for="search-field">Search</label><input type="search" class="live-search"/>
|
381
|
-
<spinner id="search-spinner"/>
|
382
|
-
</div>
|
383
|
-
<section class="hidden" id="search-results-panel">
|
384
|
-
<h2>Search Results</h2><div param="close-button">close</div>
|
385
|
-
<section id="search-results"> </section>
|
386
|
-
</section>
|
387
|
-
</def>
|
388
|
-
|
389
|
-
|
390
|
-
<def tag="a-or-an" attrs="word"><%=
|
391
|
-
(word =~ /^[aeiou]/i ? "an " : "a ") + word
|
392
|
-
%></def>
|
393
|
-
|
394
|
-
|
395
|
-
<def tag="A-or-An" attrs="word"><%=
|
396
|
-
(word =~ /^[aeiou]/i ? "An " : "A ") + word
|
397
|
-
%></def>
|
398
|
-
|
399
|
-
|
400
|
-
<def tag="filter-menu" attrs="param-name, options, no-filter">
|
401
|
-
<% no_filter ||= "All" %>
|
402
|
-
<form action="&request.request_uri" method="get" class="filter-menu">
|
403
|
-
<div>
|
404
|
-
<input type="hidden" name="filter-parameter" value="¶m_name"/>
|
405
|
-
<select-menu name="¶m_name" options="&options" selected="¶ms[param_name.gsub('-', '_')]" first-option="&no_filter" merge-params/>
|
406
|
-
</div>
|
407
|
-
</form>
|
408
|
-
</def>
|
409
|
-
|
410
|
-
|
411
|
-
<def tag="comma-list" attrs="separator"><%= this.join(separator || ", ") %></def>
|
412
|
-
|