hobo 1.0.3 → 1.1.0.pre0

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 (64) hide show
  1. data/CHANGES.txt +0 -66
  2. data/README +3 -0
  3. data/Rakefile +7 -7
  4. data/doctest/model.rdoctest +0 -2
  5. data/doctest/multi_model_forms.rdoctest +0 -2
  6. data/doctest/scopes.rdoctest +13 -21
  7. data/lib/active_record/association_collection.rb +7 -16
  8. data/lib/hobo.rb +10 -65
  9. data/lib/hobo/accessible_associations.rb +1 -5
  10. data/lib/hobo/authentication_support.rb +1 -1
  11. data/lib/hobo/controller.rb +5 -5
  12. data/lib/hobo/hobo_helper.rb +0 -84
  13. data/lib/hobo/lifecycles/lifecycle.rb +37 -31
  14. data/lib/hobo/lifecycles/transition.rb +1 -2
  15. data/lib/hobo/model.rb +13 -21
  16. data/lib/hobo/model_controller.rb +8 -8
  17. data/lib/hobo/rapid_helper.rb +12 -1
  18. data/lib/hobo/scopes/automatic_scopes.rb +26 -13
  19. data/lib/hobo/scopes/named_scope_extensions.rb +16 -28
  20. data/lib/hobo/user_controller.rb +1 -0
  21. data/lib/hobo/view_hints.rb +1 -5
  22. data/rails_generators/hobo/templates/initializer.rb +1 -1
  23. data/rails_generators/hobo_front_controller/templates/summary.dryml +4 -2
  24. data/rails_generators/hobo_model/hobo_model_generator.rb +12 -0
  25. data/rails_generators/hobo_model/templates/model.rb +9 -2
  26. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +98 -23
  27. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +1 -1
  28. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +3 -1
  29. data/{dryml_generators → rapid_generators}/rapid/cards.dryml.erb +0 -0
  30. data/{dryml_generators → rapid_generators}/rapid/forms.dryml.erb +0 -0
  31. data/{dryml_generators → rapid_generators}/rapid/pages.dryml.erb +1 -1
  32. data/taglibs/rapid.dryml +2 -0
  33. data/taglibs/rapid_core.dryml +10 -9
  34. data/taglibs/rapid_forms.dryml +70 -35
  35. data/taglibs/rapid_lifecycles.dryml +17 -4
  36. data/taglibs/rapid_plus.dryml +3 -3
  37. data/taglibs/rapid_summary.dryml +11 -0
  38. data/taglibs/rapid_user_pages.dryml +39 -28
  39. data/tasks/hobo_tasks.rake +1 -1
  40. metadata +45 -61
  41. data/hobo.gemspec +0 -226
  42. data/lib/hobo/dryml.rb +0 -188
  43. data/lib/hobo/dryml/dryml_builder.rb +0 -140
  44. data/lib/hobo/dryml/dryml_doc.rb +0 -159
  45. data/lib/hobo/dryml/dryml_generator.rb +0 -263
  46. data/lib/hobo/dryml/dryml_support_controller.rb +0 -13
  47. data/lib/hobo/dryml/parser.rb +0 -3
  48. data/lib/hobo/dryml/parser/attribute.rb +0 -41
  49. data/lib/hobo/dryml/parser/base_parser.rb +0 -254
  50. data/lib/hobo/dryml/parser/document.rb +0 -57
  51. data/lib/hobo/dryml/parser/element.rb +0 -27
  52. data/lib/hobo/dryml/parser/elements.rb +0 -45
  53. data/lib/hobo/dryml/parser/source.rb +0 -58
  54. data/lib/hobo/dryml/parser/text.rb +0 -13
  55. data/lib/hobo/dryml/parser/tree_parser.rb +0 -67
  56. data/lib/hobo/dryml/part_context.rb +0 -137
  57. data/lib/hobo/dryml/scoped_variables.rb +0 -42
  58. data/lib/hobo/dryml/tag_parameters.rb +0 -36
  59. data/lib/hobo/dryml/taglib.rb +0 -123
  60. data/lib/hobo/dryml/template.rb +0 -1019
  61. data/lib/hobo/dryml/template_environment.rb +0 -613
  62. data/lib/hobo/dryml/template_handler.rb +0 -187
  63. data/lib/hobo/static_tags +0 -98
  64. data/taglibs/core.dryml +0 -104
@@ -70,6 +70,7 @@ module Hobo
70
70
  user = model.authenticate(params[:login], params[:password])
71
71
  if user.nil?
72
72
  flash[:error] = options[:failure_notice]
73
+ hobo_ajax_response if request.xhr? && !performed?
73
74
  else
74
75
  old_user = current_user
75
76
  self.current_user = user
@@ -3,11 +3,7 @@ module Hobo
3
3
  class ViewHints
4
4
 
5
5
  def self.enable
6
- if ActiveSupport::Dependencies.respond_to?(:autoload_paths)
7
- ActiveSupport::Dependencies.autoload_paths |= [ "#{RAILS_ROOT}/app/viewhints" ]
8
- else
9
- ActiveSupport::Dependencies.load_paths |= [ "#{RAILS_ROOT}/app/viewhints" ]
10
- end
6
+ ActiveSupport::Dependencies.load_paths |= ["#{RAILS_ROOT}/app/viewhints"]
11
7
  end
12
8
 
13
9
  def self.setter(name, default=nil, &block)
@@ -1,2 +1,2 @@
1
1
  Hobo::ModelRouter.reload_routes_on_every_request = true
2
- # Hobo::Dryml.precompile_taglibs if File.basename($0) != "rake" && Rails.env.production?
2
+ # Dryml.precompile_taglibs if File.basename($0) != "rake" && Rails.env.production?
@@ -67,11 +67,12 @@
67
67
 
68
68
  <h2>Models</h2>
69
69
  <table class="app-summary">
70
- <tr><th>Class</th><th>Table</th></tr>
70
+ <tr><th>Class</th><th>Table</th><th></th></tr>
71
71
  <with-models>
72
72
  <tr>
73
73
  <td><model-name/></td>
74
74
  <td><model-table-name/></td>
75
+ <td><model-table-comment/></td>
75
76
  </tr>
76
77
  </with-models>
77
78
  </table>
@@ -80,10 +81,11 @@
80
81
  <h3 if="&this.try.table_name"><model-name /></h3>
81
82
  <table class="app-summary">
82
83
  <with-model-columns>
83
- <tr if="&first_item?"><th>Column</th><th>Type</th></tr>
84
+ <tr if="&first_item?"><th>Column</th><th>Type</th><th></th></tr>
84
85
  <tr>
85
86
  <td><model-column-name/></td>
86
87
  <td><model-column-type/></td>
88
+ <td><model-column-comment/></td>
87
89
  </tr>
88
90
  </with-model-columns>
89
91
  </table>
@@ -27,5 +27,17 @@ class HoboModelGenerator < Rails::Generator::NamedBase
27
27
  def max_attribute_length
28
28
  attributes.*.name.*.length.max
29
29
  end
30
+
31
+ def field_attributes
32
+ attributes.reject { |a| a.name == "bt" || a.name == "hm" }
33
+ end
34
+
35
+ def hms
36
+ attributes.select { |a| a.name == "hm" }.*.type
37
+ end
38
+
39
+ def bts
40
+ attributes.select { |a| a.name == "bt" }.*.type
41
+ end
30
42
 
31
43
  end
@@ -3,13 +3,20 @@ class <%= class_name %> < ActiveRecord::Base
3
3
  hobo_model # Don't put anything above this
4
4
 
5
5
  fields do
6
- <% for attribute in attributes -%>
6
+ <% for attribute in field_attributes -%>
7
7
  <%= "%-#{max_attribute_length}s" % attribute.name %> :<%= attribute.type %>
8
8
  <% end -%>
9
9
  timestamps
10
10
  end
11
11
 
12
-
12
+ <% for bt in bts -%>
13
+ belongs_to :<%= bt %>
14
+ <% end -%>
15
+ <%= "\n" unless bts.empty? -%>
16
+ <% for hm in hms -%>
17
+ has_many :<%= hm %>, :dependent => :destroy
18
+ <% end -%>
19
+ <%= "\n" unless hms.empty? -%>
13
20
  # --- Permissions --- #
14
21
 
15
22
  def create_permitted?
@@ -408,8 +408,8 @@ var Hobo = {
408
408
 
409
409
 
410
410
  updateElement: function(id, content) {
411
- // TODO: Do we need this method?
412
411
  Element.update(id, content)
412
+ Element.fire($(id), "rapid:partupdated")
413
413
  },
414
414
 
415
415
  getStyle: function(el, styleProp) {
@@ -560,7 +560,7 @@ HoboBehavior = Class.create({
560
560
  })
561
561
 
562
562
 
563
- new HoboBehavior("ul.input-many", {
563
+ HoboInputMany = {
564
564
 
565
565
  events: {
566
566
  "> li > div.buttons": {
@@ -569,9 +569,9 @@ new HoboBehavior("ul.input-many", {
569
569
  }
570
570
  },
571
571
 
572
- initialize: function(ul) {
572
+ initialize: function(ev) {
573
573
  /* the second clause should be sufficient, but it isn't in IE7. See bug 603 */
574
- $$(".input-many-template input:hidden, .input-many-template select:hidden, .input-many-template textarea:hidden, .input-many-template button:hidden").each(function(input) {
574
+ Element.select(ev.target, ".input-many-template input:hidden, .input-many-template select:hidden, .input-many-template textarea:hidden, .input-many-template button:hidden").each(function(input) {
575
575
  if(!input.disabled) {
576
576
  input.disabled = true;
577
577
  input.addClassName("input_many_template_input");
@@ -579,10 +579,33 @@ new HoboBehavior("ul.input-many", {
579
579
  });
580
580
 
581
581
  // disable all elements inside our template, and mark them so we can find them later.
582
- $$(".input-many-template input:enabled, .input-many-template select:enabled, .input-many-template textarea:enabled, .input-many-template button:enabled").each(function(input) {
582
+ Element.select(ev.target, ".input-many-template input:enabled, .input-many-template select:enabled, .input-many-template textarea:enabled, .input-many-template button:enabled").each(function(input) {
583
583
  input.disabled = true;
584
584
  input.addClassName("input_many_template_input");
585
585
  });
586
+
587
+ Element.select(ev.target, ".sortable-input-many").each(function(el) {
588
+ HoboInputMany.createSortable.call(el);
589
+ });
590
+
591
+ /* need to reinitialize after every change */
592
+ Event.addBehavior({".sortable-input-many:rapid:change": function(ev) {
593
+ HoboInputMany.createSortable.call(this);
594
+ }});
595
+
596
+ document.observe("rapid:partupdated", HoboInputMany.initialize);
597
+ },
598
+
599
+ createSortable: function() {
600
+ Sortable.create(this.id, {
601
+ constraint: 'vertical',
602
+ handle: 'ordering-handle',
603
+ overlap: 'vertical',
604
+ scroll: 'window',
605
+ onUpdate: function(list) {
606
+ HoboInputMany.fixIndices.call(list);
607
+ }
608
+ });
586
609
  },
587
610
 
588
611
  // given this==the input-many, returns a lambda that updates the name & id for an element
@@ -619,23 +642,56 @@ new HoboBehavior("ul.input-many", {
619
642
 
620
643
  // given this==an input-many item, get the submit index
621
644
  getIndex: function() {
622
- return Number(this.id.match(/\[([-0-9]+)\]$/)[1]);
645
+ return Number(this.id.match(/_([-0-9]+)$/)[1]);
623
646
  },
624
647
 
625
- /* For some reason, select() and down() and all those useful functions aren't working for us. Roll our own replacement. */
626
- recurse_elements_with_class: function(el, klass, f) {
627
- var that=this;
628
- if(klass==null || el.hasClassName(klass)) {
629
- f(el);
648
+ /* For some reason, select() and down() and all those useful functions aren't working for us. Roll our own replacement.
649
+
650
+ this: element to recurse on.
651
+ klass: class to filter on
652
+ f: function to invoke
653
+ */
654
+ recurse_elements_with_class: function(klass,f ) {
655
+ if(klass==null || this.hasClassName(klass)) {
656
+ f(this);
630
657
  }
631
- el.childElements().each(function(el2) {that.recurse_elements_with_class.call(that, el2, klass, f);});
658
+ this.childElements().each(function(el2) {HoboInputMany.recurse_elements_with_class.call(el2, klass, f);});
632
659
  },
633
660
 
661
+ /* fixes the indices on an input-many so they're in order. */
662
+ fixIndices: function() {
663
+ var lis = this.immediateDescendants();
664
+ var minimum = parseInt(Hobo.getClassData(this, 'minimum'));
665
+ /* first two lis are hidden/disabled on an input-many */
666
+ for(var i=0; i<lis.length-2; i++) {
667
+ var il=i+2;
668
+ if(i!=HoboInputMany.getIndex.call(lis[il])) {
669
+ var updater = HoboInputMany.getNameUpdater.call(this, i);
670
+ HoboInputMany.recurse_elements_with_class.call(lis[il], null, function(el) {
671
+ updater.call(el);
672
+ });
673
+ var position=lis[il].childWithClass("sortable-position");
674
+ if(position) position.value=i+1;
675
+ if(i==minimum-1 && il==lis.length-1) {
676
+ lis[il].childWithClass("buttons").childWithClass("remove-item").addClassName("hidden");
677
+ } else {
678
+ lis[il].childWithClass("buttons").childWithClass("remove-item").removeClassName("hidden");
679
+ }
680
+ if(il==lis.length-1) {
681
+ lis[il].childWithClass("buttons").childWithClass("add-item").removeClassName("hidden");
682
+ } else {
683
+ lis[il].childWithClass("buttons").childWithClass("add-item").addClassName("hidden");
684
+ }
685
+ }
686
+ }
687
+ },
688
+
689
+
634
690
  addOne: function(ev, el) {
635
691
  Event.stop(ev);
636
692
  var ul = el.up('ul.input-many'), li = el.up('li.input-many-li');
637
693
 
638
- if(li.id.search(/\[-1\]/)>=0 && ul.immediateDescendants().length>2) {
694
+ if(li.id.search(/_-1$/ && ul.immediateDescendants().length>2)>=0) {
639
695
  /* if(console) console.log("IE7 messed up again (bug 605)"); */
640
696
  return;
641
697
  }
@@ -658,7 +714,7 @@ new HoboBehavior("ul.input-many", {
658
714
  reenable_inputs(clone);
659
715
 
660
716
  // update id & name
661
- this.recurse_elements_with_class.call(this, clone, null, function(el) {
717
+ HoboInputMany.recurse_elements_with_class.call(clone, null, function(el) {
662
718
  name_updater.call(el);
663
719
  });
664
720
 
@@ -691,7 +747,7 @@ new HoboBehavior("ul.input-many", {
691
747
  var ul = el.up('ul.input-many'), li = el.up('li.input-many-li')
692
748
  var minimum = parseInt(Hobo.getClassData(ul, 'minimum'));
693
749
 
694
- if(li.id.search(/\[-1\]/)>=0) {
750
+ if(li.id.search(/_-1$/)>=0) {
695
751
  /* if(console) console.log("IE7 messed up again (bug 605)"); */
696
752
  return;
697
753
  }
@@ -703,7 +759,7 @@ new HoboBehavior("ul.input-many", {
703
759
  var n=li.next();
704
760
  for(; n; i+=1, n=n.next()) {
705
761
  var name_updater = this.getNameUpdater.call(ul, i);
706
- this.recurse_elements_with_class.call(this, n, null, function(el) {name_updater.call(el);});
762
+ HoboInputMany.recurse_elements_with_class.call(n, null, function(el) {name_updater.call(el);});
707
763
  }
708
764
 
709
765
  // adjust +/- buttons on the button element as appropriate
@@ -714,7 +770,7 @@ new HoboBehavior("ul.input-many", {
714
770
 
715
771
  if(last.hasClassName("empty")) {
716
772
  last.removeClassName("hidden");
717
- this.recurse_elements_with_class.call(this, last, "empty-input", function(el) {el.disabled=false;});
773
+ HoboInputMany.recurse_elements_with_class.call(last, "empty-input", function(el) {el.disabled=false;});
718
774
  } else {
719
775
  // if we've reached the minimum, we don't want to add the '-' button
720
776
  if(ul.childElements().length-3 <= minimum||0) {
@@ -734,7 +790,9 @@ new HoboBehavior("ul.input-many", {
734
790
 
735
791
 
736
792
 
737
- })
793
+ }
794
+
795
+ new HoboBehavior("ul.input-many", HoboInputMany);
738
796
 
739
797
 
740
798
  SelectManyInput = Behavior.create({
@@ -811,9 +869,10 @@ NameManyInput = Object.extend(SelectManyInput, {
811
869
  }
812
870
  })
813
871
 
814
-
872
+
815
873
  AutocompleteBehavior = Behavior.create({
816
874
  initialize : function() {
875
+ this.minChars = parseInt(Hobo.getClassData(this.element, "min-chars"));
817
876
  var match = this.element.className.match(/complete-on::([\S]+)/)
818
877
  var target = match[1].split('::')
819
878
  var typedId = target[0]
@@ -822,11 +881,27 @@ AutocompleteBehavior = Behavior.create({
822
881
  var spec = Hobo.parseModelSpec(typedId)
823
882
  var url = urlBase + "/" + Hobo.pluralise(spec.name) + "/complete_" + completer
824
883
  var parameters = spec.id ? "id=" + spec.id : ""
825
- new Ajax.Autocompleter(this.element,
826
- this.element.next('.completions-popup'),
827
- url,
828
- {paramName:'query', method:'get', parameters: parameters});
884
+ this.autocompleter = new Ajax.Autocompleter(this.element,
885
+ this.element.next('.completions-popup'),
886
+ url,
887
+ {paramName:'query', method:'get', parameters: parameters, minChars: this.minChars,
888
+ afterUpdateElement: this.afterUpdateElement});
889
+ },
890
+
891
+ onfocus: function() {
892
+ if(this.element.hasClassName("nil-value")) {
893
+ this.element.value = '';
894
+ this.element.removeClassName("nil-value");
895
+ }
896
+ if(this.minChars==0) {
897
+ this.autocompleter.activate();
898
+ }
899
+ },
900
+
901
+ afterUpdateElement: function(input, li) {
902
+ input.fire("rapid:autocomplete-assigned");
829
903
  }
904
+
830
905
  })
831
906
 
832
907
 
@@ -57,7 +57,7 @@ pre, code {
57
57
  font-family: "Courier New", Courier, monospace;
58
58
  }
59
59
 
60
- input.text, input.string, input.email-address, input.password, input.search, input.integer, input.float, textarea {
60
+ input.text, input.string, input.email-address, input.password, input.search, input.integer, input.float, input.autocompleter, input.decimal, textarea {
61
61
  border-top:1px solid #7c7c7c;
62
62
  border-left:1px solid #c3c3c3;
63
63
  border-right:1px solid #c3c3c3;
@@ -97,4 +97,6 @@ select.dev-user-changer:hover { opacity: 1; }
97
97
  optgroup.disabled-option {
98
98
  color: #ccc;
99
99
  height: 1em;
100
- }
100
+ }
101
+
102
+ input.nil-value { color:grey; }
@@ -165,7 +165,7 @@ end
165
165
  <% end -%>
166
166
  </header>
167
167
 
168
- <section param="content-body">
168
+ <section param="content-body"<%= ' with-flash-messages' if aside_collections %>>
169
169
  <% if main_content -%>
170
170
  <view:<%= main_content %> param="description"/>
171
171
  <% end -%>
@@ -6,7 +6,9 @@ The Rapid tag library makes web development go fast. The Rapid tag library is yo
6
6
 
7
7
  -->
8
8
 
9
+ <include module="Hobo::HoboHelper"/>
9
10
  <include module="Hobo::RapidHelper"/>
11
+ <include module="Hobo::Translations"/>
10
12
 
11
13
  <include src="rapid_core"/>
12
14
  <include src="rapid_support"/>
@@ -1,3 +1,4 @@
1
+ <!-- Core Rapid tags and tags that don't belong anywhere else. -->
1
2
 
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)
3
4
  a `<view>` of the field.
@@ -35,11 +36,11 @@
35
36
  input_attrs = {:no_edit => no_edit} if tag == "input"
36
37
  -%>
37
38
  <labelled-item unless="&tag == 'input' && no_edit == 'skip' && !can_edit?">
38
- <item-label param="#{scope.field_name.to_s.sub('?', '').sub('.', '-')}-label" unless="&field_name.blank?">
39
+ <item-label param="#{scope.field_name.to_s.sub('?', '').gsub('.', '-')}-label" unless="&field_name.blank?">
39
40
  <do param="label"><%= field_name %></do>
40
41
  </item-label>
41
- <item-value param="#{scope.field_name.to_s.sub('?', '').sub('.', '-')}-view" colspan="&2 if field_name.blank?">
42
- <do param="view"><call-tag tag="&tag" param="#{scope.field_name.to_s.sub('?', '').sub('.', '-')}-tag" merge-attrs="&input_attrs"/></do>
42
+ <item-value param="#{scope.field_name.to_s.sub('?', '').gsub('.', '-')}-view" colspan="&2 if field_name.blank?">
43
+ <do param="view"><call-tag tag="&tag" param="#{scope.field_name.to_s.sub('?', '').gsub('.', '-')}-tag" merge-attrs="&input_attrs"/></do>
43
44
  <div param="input-help" if="&tag.to_sym == :input && !this_field_help.blank?"><%= this_field_help %></div>
44
45
  </item-value>
45
46
  </labelled-item>
@@ -107,7 +108,7 @@ This will use `<input/>` as the tag in each table cell instead of `<view/>`
107
108
  <thead if="&all_parameters[:thead] || fields" param>
108
109
  <tr param="field-heading-row">
109
110
  <with-field-names merge-attrs="&all_attributes & attrs_for(:with_fields)">
110
- <th param="#{scope.field_name}-heading"><%= this.member_class.try.view_hints.try.field_name(scope.field_name) if scope %></th>
111
+ <th param="#{scope.field_name}-heading"><%= (this.member_class.try.view_hints.try.field_name(scope.field_name) || scope.field_name.titleize) if scope %></th>
111
112
  </with-field-names>
112
113
  <th if="&all_parameters[:controls]" class="controls"/>
113
114
  </tr>
@@ -335,7 +336,7 @@ Or a new page if the context is a class:
335
336
  target = to || this
336
337
 
337
338
  if target.nil?
338
- Hobo::Dryml.last_if = false
339
+ Dryml.last_if = false
339
340
  nil_view
340
341
  elsif action == "new"
341
342
  # Link to a new object form
@@ -352,10 +353,10 @@ Or a new page if the context is a class:
352
353
 
353
354
  add_classes!(attributes, "new-#{new_class_name.underscore}-link")
354
355
  content = "New #{new_class_name.titleize}" if content.blank?
355
- Hobo::Dryml.last_if = true
356
+ Dryml.last_if = true
356
357
  element(:a, attributes.update(:href => href), content)
357
358
  else
358
- Hobo::Dryml.last_if = false
359
+ Dryml.last_if = false
359
360
  ""
360
361
  end
361
362
  else
@@ -456,7 +457,7 @@ Assuming the context is a blog post...
456
457
  end
457
458
  end
458
459
  end
459
- Hobo::Dryml.last_if = !res.blank?
460
+ Dryml.last_if = !res.blank?
460
461
  res
461
462
  %></def>
462
463
 
@@ -535,7 +536,7 @@ The label can be customised using the `label` attribute, e.g.
535
536
 
536
537
  label = label.downcase if lowercase
537
538
 
538
- Hobo::Dryml.last_if = c > 0 if if_any
539
+ Dryml.last_if = c > 0 if if_any
539
540
  if if_any && c == 0
540
541
  ""
541
542
  else
@@ -51,8 +51,7 @@ executed at various points in the ajax request cycle:
51
51
  hiddens = case fields
52
52
  when '*', nil
53
53
  # TODO: Need a better (i.e. extensible) way to eleminate certain fields
54
- # marking a field as attr_protected is one way to eliminate a field
55
- this.class.column_names - [this.class.inheritance_column, 'created_at', 'updated_at']
54
+ this.class.column_names - ['type', 'created_at', 'updated_at']
56
55
  else
57
56
  comma_split(fields)
58
57
  end
@@ -103,6 +102,8 @@ AJAX based submission can be enabled by simply adding an `update` attribute. e.g
103
102
  <div part="comments"><collection:comments/></div>
104
103
  <form with="&Comment.new" update="comments"/>
105
104
 
105
+ `<form>` support all of the standard ajax attributes.
106
+
106
107
  ### Additional Notes
107
108
 
108
109
  - Hobo automatically inserts an `auth_token` hidden field if forgery protection is enabled
@@ -111,19 +112,6 @@ AJAX based submission can be enabled by simply adding an `update` attribute. e.g
111
112
  validation error occurs.
112
113
 
113
114
  - `<form>` supports all of the standrd ajax attributes - (see the main taglib docs for Rapid Forms)
114
-
115
- - `<form>` resets `last_if` if it does not have permission to display the form. The `<else>` clause may be used to display alternate content. For example:
116
-
117
- <form>...</form>
118
- <else>You do not have permission to edit this form</else>
119
-
120
- or on a standard generated page using a default form:
121
-
122
- <some-page>
123
- <after-form:>
124
- <else>You do not have permission to edit this form</else>
125
- </after-form:>
126
- </some-page>
127
115
 
128
116
  ### Attributes
129
117
 
@@ -186,7 +174,7 @@ The standard form tag does not have any parameters, nor does it have any default
186
174
  if action.nil? && (html_attrs[:action].nil? ||
187
175
  (lifecycle.nil? && new_record && !this.creatable_by?(current_user)) ||
188
176
  (lifecycle.nil? && !new_record && !can_edit?))
189
- Hobo::Dryml.last_if = false
177
+ Dryml.last_if = false
190
178
  ""
191
179
  else
192
180
  if method == "put"
@@ -223,7 +211,7 @@ The standard form tag does not have any parameters, nor does it have any default
223
211
  page_path = if (request.post? || request.put?) && params[:page_path]
224
212
  params[:page_path]
225
213
  else
226
- view_name.sub(Hobo::Dryml::EMPTY_PAGE, params[:action] || '')
214
+ view_name.sub(Dryml::EMPTY_PAGE, params[:action] || '')
227
215
  end
228
216
  page_path_hidden = hidden_field_tag("page_path", page_path)
229
217
  end
@@ -240,7 +228,7 @@ The standard form tag does not have any parameters, nor does it have any default
240
228
  end
241
229
  end
242
230
 
243
- Hobo::Dryml.last_if = true
231
+ Dryml.last_if = true
244
232
  element("form", html_attrs, body)
245
233
  end
246
234
  %></def>
@@ -350,12 +338,11 @@ edit collections a `Category` model in your application:
350
338
  <def tag="collection-input" for="ActiveRecord::Base"><select-many merge/></def>
351
339
 
352
340
 
353
- <!-- A `<textarea>` input. Attributes are passed through to [text_area_tag](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-text_area_tag). -->
341
+ <!-- A `<textarea>` input -->
354
342
  <def tag="input" for="text" attrs="name">
355
- <%= text_area_tag(name, attributes["escape"]==false ? this : html_escape(this), attributes) %>
343
+ <%= text_area_tag(name, this, attributes) %>
356
344
  </def>
357
345
 
358
-
359
346
  <!-- A checkbox plus a hidden-field. The hidden field trick comes from Rails - it means that when the checkbox is not checked, the parameter name is still submitted, with a '0' value (the value is '1' when the checkbox is checked) -->
360
347
  <def tag="input" for="boolean" attrs="name">
361
348
  <%= unless attributes[:disabled]
@@ -570,7 +557,7 @@ All the standard ajax attributes *except the callbacks* are supported (see the m
570
557
  <def tag="delete-button" attrs="label, update, in-place, image, confirm, fade, subsite"><%=
571
558
  in_place = false if in_place.nil? && this == @this && request.method == :get
572
559
  url = object_url(this, :method => :delete, :subsite => subsite)
573
- if (Hobo::Dryml.last_if = url && can_delete?)
560
+ if (Dryml.last_if = url && can_delete?)
574
561
  attributes = attributes.merge(if image
575
562
  { :type => "image", :src => "#{base_url}/images/#{image}" }
576
563
  else
@@ -730,14 +717,26 @@ We're using an object as the complete-target rather than a class. This allows
730
717
 
731
718
  There's another example of `<name-one>` use in the [recipes](http://cookbook.hobocentral.net/recipes/36-using-a-name-one-one-a).
732
719
 
720
+ ### Attributes:
721
+
722
+ - `complete-target`, `completer`: see above
723
+ - `min-chars`: The minimum number of characters that must be entered in the input field before an Ajax request is made.
724
+ - `nil-value`: If there is no current value, this text will appear greyed out inside the control, and will disappear on focus.
725
+
726
+ ### Note:
727
+
728
+ If you wish to set `min-chars` to 0, you will require this [patch to controls.js](http://github.com/bryanlarsen/scriptaculous/commit/3915b7b). 'controls.js' was added to your project via the rails generator, not via Hobo.
729
+
733
730
  -->
734
- <def tag="name-one" attrs="complete-target, completer"><%
731
+ <def tag="name-one" attrs="complete-target, completer, min-chars, nil-value"><%
735
732
  complete_target ||= this_field_reflection.klass
736
733
  completer ||= (complete_target.is_a?(Class) ? complete_target : complete_target.class).name_attribute
734
+ min_chars ||= 1
735
+ value = name(:no_wrapper => true, :if_present => true)
737
736
  -%>
738
737
  <input type="text" name="#{param_name_for_this}"
739
- class="autocompleter #{type_and_field.dasherize} #{css_data :complete_on, typed_id(complete_target), completer}"
740
- value="&name :no_wrapper => true, :if_present => true"
738
+ class="autocompleter #{type_and_field._?.dasherize} #{css_data :complete_on, typed_id(complete_target), completer} #{css_data :min_chars, min_chars} #{'nil-value' if value==''}"
739
+ value="#{value=='' ? nil_value : value}"
741
740
  merge-attrs/>
742
741
  <div class="completions-popup" style="display:none"></div>
743
742
  </def>
@@ -976,8 +975,11 @@ Example javascript:
976
975
  }
977
976
  });
978
977
 
978
+ Note: if your javascript does not work, please ensure that you have
979
+ the Hobo version of lowpro.js.
980
+
979
981
  -->
980
- <def tag="input-many" attrs="minimum, fields, skip, template" polymorphic >
982
+ <def tag="input-many" attrs="minimum, fields, skip, more-skip, template" polymorphic >
981
983
  <%
982
984
  # helper function to create id's on buttons to facilitate testing
983
985
  def underize(s)
@@ -987,10 +989,11 @@ end
987
989
  <set empty="&this.empty?"/>
988
990
  <% template ||= this.try.new_candidate || this.member_class.new %>
989
991
  <% minimum ||= 0 ; minimum = minimum.to_i %>
990
- <% skip ||= this.proxy_reflection.klass.reflect_on_all_associations.detect {|p| p.primary_key_name==this.proxy_reflection.primary_key_name}.try.name.to_s if this.respond_to? :proxy_reflection %>
991
- <ul class="input-many #{this_field.dasherize} #{css_data :input_many_prefix, param_name_for_this} #{css_data(:minimum, minimum)}">
992
+ <% skip ||= this.proxy_reflection.klass.reflect_on_all_associations.detect {|p| p.primary_key_name==this.proxy_reflection.primary_key_name}.try.name.to_s if this.respond_to? :proxy_reflection %>
993
+ <% skip += ",#{more_skip}" if more_skip -%>
994
+ <ul class="input-many #{this_field.dasherize} #{css_data :input_many_prefix, param_name_for_this} #{css_data(:minimum, minimum)}" merge-attrs>
992
995
  <fake-field-context fake-field="-1" context="&template">
993
- <li class="input-many-li input-many-template" id="#{param_name_for_this}">
996
+ <li class="input-many-li input-many-template" id="#{underize param_name_for_this}">
994
997
  <div class="input-many-item" param="default">
995
998
  <field-list param merge-attrs="fields" skip="&skip" />
996
999
  </div>
@@ -1000,13 +1003,13 @@ end
1000
1003
  </div>
1001
1004
  </li>
1002
1005
  </fake-field-context>
1003
- <li class="input-many-li empty #{'hidden' unless this.empty? and minimum==0}" id="#{param_name_for_this}[-1]_empty">
1006
+ <li class="input-many-li empty #{'hidden' unless this.empty? and minimum==0}" id="#{underize param_name_for_this}_-1_empty">
1004
1007
  <!-- HACK way to signal an empty collection to the controller -->
1005
- <input type="hidden" class="empty-input" id="#{param_name_for_this}" name="#{param_name_for_this}" value="" disabled="&(!this.empty? || minimum>0)" />
1008
+ <input type="hidden" class="empty-input" id="#{underize param_name_for_this}" name="#{param_name_for_this}" value="" disabled="&(!this.empty? || minimum>0)" />
1006
1009
  <fake-field-context fake-field="-1" context="&template">
1007
1010
  <div param="empty-message">
1008
- <ht key="#{this.class.name.tableize}.collection.empty_message">
1009
- No <%= this.class.name.titleize.downcase.pluralize %>.
1011
+ <ht key="#{this.class.class_name.tableize}.collection.empty_message">
1012
+ No <%= this.class.class_name.titleize.downcase.pluralize %>.
1010
1013
  </ht>
1011
1014
  </div>
1012
1015
  <div class="buttons">
@@ -1016,7 +1019,7 @@ end
1016
1019
  </fake-field-context>
1017
1020
  </li>
1018
1021
  <fake-field-context fake-field="0" context="&template">
1019
- <li class="input-many-li" if="&(this_parent.empty? && minimum>0)" id="#{param_name_for_this}">
1022
+ <li class="input-many-li" if="&(this_parent.empty? && minimum>0)" id="#{underize param_name_for_this}">
1020
1023
  <div class="input-many-item" param="default">
1021
1024
  <field-list param merge-attrs="fields" skip="&skip" />
1022
1025
  </div>
@@ -1026,7 +1029,7 @@ end
1026
1029
  </div>
1027
1030
  </li>
1028
1031
  </fake-field-context>
1029
- <li repeat class="input-many-li #{'record-with-errors' unless this.errors.empty?}" id="#{param_name_for_this}">
1032
+ <li repeat class="input-many-li #{'record-with-errors' unless this.errors.empty?}" id="#{underize param_name_for_this}">
1030
1033
  <error-messages without-heading class="sub-record"/>
1031
1034
  <hidden-id-field/>
1032
1035
  <div class="input-many-item" param="default">
@@ -1055,6 +1058,38 @@ end
1055
1058
  </ul>
1056
1059
  </def>
1057
1060
 
1061
+ <!-- An enhanced version of [`<input-many>`](/api_tag_defs/input-many) that supports drag-and-drop re-ordering.
1062
+
1063
+ Each item in the collection has a `<div class="ordering-handle" param="handle">` added, which can be used to drag the item up and down.
1064
+
1065
+ If the items in the collection contain an [`acts_as_list`](http://ar.rubyonrails.org/classes/ActiveRecord/Acts/List/ClassMethods.html) declaration, it is updated.
1066
+
1067
+ The specified sort order may be maintained even without `acts_as_list`. The items will be passed to the controller in the correct order, so the order may be persisted there.
1068
+
1069
+ ### Attributes
1070
+
1071
+ - `id`: Due to a limitation in script.aculo.us, an id is required. If you do not supply one, one will be generated.
1072
+ - `position-column`: The position column may be specified via `acts_as_list`, via a `position_column` method on your model or via this attribute.
1073
+ - others: all other attributes are passed through to `<input-many>`
1074
+
1075
+ -->
1076
+
1077
+ <def tag="sortable-input-many" attrs="id, position-column, template">
1078
+ <% this_id = this_parent.id || rand(100000) -%>
1079
+ <% id ||= "sortable-input-many-#{this_parent.class.name.underscore.dasherize}-#{this_id}-#{this_field_reflection.name}" -%>
1080
+ <% template ||= this.try.new_candidate || this.member_class.new %>
1081
+ <% position_column ||= template.try.position_column -%>
1082
+ <input-many merge id="&id" class="sortable-input-many" template="&template" more-skip="&position_column">
1083
+ <default: replace>
1084
+ <div class="ordering-handle" param="handle" if="&can_edit?">&uarr;<br/>&darr;</div>
1085
+ <if test="&position_column">
1086
+ <input class="sortable-position" type="hidden" value="&this.send(position_column)" name="#{param_name_for_this}[#{position_column}]" />
1087
+ </if>
1088
+ <default restore/>
1089
+ </default:>
1090
+ </input-many>
1091
+ </def>
1092
+
1058
1093
  <!-- Renders the common "or (Cancel)" for a form. Attributes are merged into the link (`<a>Cancel</a>`), making it easy to customise the destination of the cancel link. By default it will link to `this` or `this.class`.
1059
1094
  -->
1060
1095
  <def tag="or-cancel">