hobo 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. data/CHANGES.txt +330 -0
  2. data/Manifest +12 -4
  3. data/Rakefile +4 -6
  4. data/dryml_generators/rapid/cards.dryml.erb +5 -1
  5. data/dryml_generators/rapid/forms.dryml.erb +8 -10
  6. data/dryml_generators/rapid/pages.dryml.erb +65 -36
  7. data/hobo.gemspec +28 -15
  8. data/lib/active_record/association_collection.rb +3 -22
  9. data/lib/hobo.rb +25 -258
  10. data/lib/hobo/accessible_associations.rb +131 -0
  11. data/lib/hobo/authentication_support.rb +15 -9
  12. data/lib/hobo/composite_model.rb +1 -1
  13. data/lib/hobo/controller.rb +7 -8
  14. data/lib/hobo/dryml.rb +9 -10
  15. data/lib/hobo/dryml/dryml_builder.rb +7 -1
  16. data/lib/hobo/dryml/dryml_doc.rb +161 -0
  17. data/lib/hobo/dryml/dryml_generator.rb +18 -9
  18. data/lib/hobo/dryml/part_context.rb +76 -42
  19. data/lib/hobo/dryml/tag_parameters.rb +1 -0
  20. data/lib/hobo/dryml/taglib.rb +2 -1
  21. data/lib/hobo/dryml/template.rb +39 -29
  22. data/lib/hobo/dryml/template_environment.rb +79 -37
  23. data/lib/hobo/dryml/template_handler.rb +66 -21
  24. data/lib/hobo/guest.rb +2 -10
  25. data/lib/hobo/hobo_helper.rb +125 -53
  26. data/lib/hobo/include_in_save.rb +0 -1
  27. data/lib/hobo/lifecycles.rb +54 -24
  28. data/lib/hobo/lifecycles/actions.rb +95 -31
  29. data/lib/hobo/lifecycles/creator.rb +18 -23
  30. data/lib/hobo/lifecycles/lifecycle.rb +86 -62
  31. data/lib/hobo/lifecycles/state.rb +1 -2
  32. data/lib/hobo/lifecycles/transition.rb +22 -28
  33. data/lib/hobo/model.rb +64 -176
  34. data/lib/hobo/model_controller.rb +67 -54
  35. data/lib/hobo/model_router.rb +5 -2
  36. data/lib/hobo/permissions.rb +397 -0
  37. data/lib/hobo/permissions/associations.rb +167 -0
  38. data/lib/hobo/scopes.rb +15 -38
  39. data/lib/hobo/scopes/association_proxy_extensions.rb +15 -5
  40. data/lib/hobo/scopes/automatic_scopes.rb +43 -18
  41. data/lib/hobo/scopes/named_scope_extensions.rb +2 -2
  42. data/lib/hobo/user.rb +10 -4
  43. data/lib/hobo/user_controller.rb +6 -5
  44. data/lib/hobo/view_hints.rb +58 -0
  45. data/rails_generators/hobo/hobo_generator.rb +7 -3
  46. data/rails_generators/hobo/templates/guest.rb +1 -13
  47. data/rails_generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
  48. data/rails_generators/hobo_model/hobo_model_generator.rb +4 -2
  49. data/rails_generators/hobo_model/templates/hints.rb +4 -0
  50. data/rails_generators/hobo_model/templates/model.rb +8 -8
  51. data/rails_generators/hobo_model_controller/hobo_model_controller_generator.rb +10 -0
  52. data/rails_generators/hobo_model_controller/templates/controller.rb +1 -1
  53. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +91 -56
  54. data/rails_generators/hobo_rapid/templates/lowpro.js +15 -15
  55. data/rails_generators/hobo_rapid/templates/reset.css +36 -3
  56. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +13 -17
  57. data/rails_generators/hobo_user_controller/templates/controller.rb +1 -1
  58. data/rails_generators/hobo_user_model/templates/model.rb +18 -16
  59. data/taglibs/core.dryml +60 -18
  60. data/taglibs/rapid.dryml +8 -401
  61. data/taglibs/rapid_core.dryml +586 -0
  62. data/taglibs/rapid_document_tags.dryml +28 -10
  63. data/taglibs/rapid_editing.dryml +92 -55
  64. data/taglibs/rapid_forms.dryml +406 -87
  65. data/taglibs/rapid_generics.dryml +1 -1
  66. data/taglibs/rapid_navigation.dryml +2 -1
  67. data/taglibs/rapid_pages.dryml +7 -16
  68. data/taglibs/rapid_plus.dryml +39 -14
  69. data/taglibs/rapid_support.dryml +1 -1
  70. data/taglibs/rapid_user_pages.dryml +14 -4
  71. data/tasks/{generate_tag_reference.rb → generate_tag_reference.rake} +49 -18
  72. data/tasks/hobo_tasks.rake +16 -0
  73. data/test/permissions/models/models.rb +134 -0
  74. data/test/permissions/models/schema.rb +55 -0
  75. data/test/permissions/models/test.sqlite3 +0 -0
  76. data/test/permissions/test_permissions.rb +436 -0
  77. metadata +27 -14
  78. data/lib/hobo/mass_assignment.rb +0 -64
  79. data/rails_generators/hobo/templates/patch_routing.rb +0 -30
  80. data/uninstall.rb +0 -1
@@ -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 && window.console && window.console.warn)
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
- return function() {
19
- var attrs, children;
20
- if (arguments.length>0) {
21
- if (arguments[0].constructor == Object) {
22
- attrs = arguments[0];
23
- children = Array.prototype.slice.call(arguments, 1);
24
- } else {
25
- children = arguments;
26
- };
27
- children = $A(children).flatten()
28
- }
29
- return DOM.Builder.create(tag, attrs, children);
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 parts = sel.split(/:(?=[a-z]+$)/), css = parts[0], event = parts[1];
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, code,
7
- del, dfn, em, font, img, ins, kbd, q, s, samp,
8
- small, strike, strong, sub, sup, tt, var,
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
- color: #cfc; background: black url(../images/spinner.gif) no-repeat 10px 8px;
107
- font-size: 13px; font-weight: bold;
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 {clear: both; margin-left: 0; list-style: none;}
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
 
@@ -2,6 +2,6 @@ class <%= class_name %>Controller < ApplicationController
2
2
 
3
3
  hobo_user_controller
4
4
 
5
- auto_actions :all, :except => [ :index, :create ]
5
+ auto_actions :all, :except => [ :index, :new, :create ]
6
6
 
7
7
  end
@@ -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
- username :string, :login => true, :name => true
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
- initial_state :active
21
+ state :active, :default => true
22
22
 
23
- create :anybody, :signup,
24
- :params => [:username, :email_address, :password, :password_confirmation],
25
- :become => :active, :if => proc {|_, u| u.guest?}
23
+ create :signup, :available_to => "Guest",
24
+ :params => [:name, :email_address, :password, :password_confirmation],
25
+ :become => :active
26
26
 
27
- transition :nobody, :request_password_reset, { :active => :active }, :new_key => true do
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 :with_key, :reset_password, { :active => :active },
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
- # --- Hobo Permissions --- #
37
+ # --- Permissions --- #
38
38
 
39
- def creatable_by?(creator)
40
- creator.administrator? || !administrator
39
+ def create_permitted?
40
+ false
41
41
  end
42
42
 
43
- def updatable_by?(updater, new)
44
- updater.administrator? || (updater == self && only_changed_fields?(new, :password, :password_confirmation))
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 deletable_by?(deleter)
48
- deleter.administrator?
49
+ def destroy_permitted?
50
+ acting_user.administrator?
49
51
  end
50
52
 
51
- def viewable_by?(viewer, field)
53
+ def view_permitted?(field)
52
54
  true
53
55
  end
54
56
 
@@ -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
- <def tag="repeat" attrs="even-odd, join"><if><%=
19
- if even_odd
20
- context_map do
21
- klass = [attributes[:class], model_id_class, cycle("even", "odd")].compact.join(' ')
22
- element(even_odd, attributes.merge(:class => klass), parameters.default)
23
- end.join(join)
24
- else
25
- scope.new_scope :even_odd => "odd" do
26
- context_map do
27
- res = parameters.default
28
- scope.even_odd = scope.even_odd == "even" ? "odd" : "even"
29
- res
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="&current_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 %></def>
103
+ res
104
+ %></def>
@@ -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">&nbsp;</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="&param_name"/>
405
- <select-menu name="&param_name" options="&options" selected="&params[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
-