hobo 1.0.3 → 1.1.0.pre0

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