hobo 0.9.102 → 0.9.103

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.
@@ -14,7 +14,51 @@ likely to cause conflicts, so it is highly recommended that you have
14
14
  your code backed up and in a change control system such as git or
15
15
  subversion.
16
16
 
17
- === Hobo 0.9.101 (AKA 1.0.BROWN_PAPER_BAG) ===
17
+ === Hobo 0.9.103 (AKA 1.0.RC2) ===
18
+
19
+
20
+ ### Warning
21
+
22
+ If you are on Rails 2.3.5 and are running Hobo as a plugin,
23
+ please check out bug
24
+ [#574](https://hobo.lighthouseapp.com/projects/8324/tickets/574-rails-235-b0rks-our-rake-tasks-running-on-edge-hobo)
25
+ for a workaround you need to apply to your Rakefile.
26
+
27
+ ### Bugs
28
+
29
+ This release fixes a couple of serious bugs:
30
+ [565](https://hobo.lighthouseapp.com/projects/8324-hobo/tickets/565)
31
+ and
32
+ [567](https://hobo.lighthouseapp.com/projects/8324-hobo/tickets/567).
33
+
34
+ ### Input-Many & has-many :through
35
+
36
+ The `<input-many>` tag in Rapid has been replaced with a version
37
+ ported from the `<hjq-input-many>` tag in Hobo-JQuery. This brings
38
+ the following enhancements:
39
+
40
+ - it supports 0 length associations
41
+ - input-many's may be nested inside of other input-many's
42
+ - it allows the (+) and (-) buttons to be customized
43
+ - it provides a default for the `item` parameter
44
+ - it copies from a template rather than cloning the current item and clearing it
45
+ - the template may be overridden
46
+ - id's of textareas and selects and other non-input's are adjusted properly
47
+ - classdata for inner elements updated
48
+
49
+ The new `<input-many>` tag differs from `<hjq-input-many>` in that:
50
+
51
+ - it's written in prototype.js rather than in jquery
52
+ - it doesn't have the delayed initialization feature
53
+ - the name of the main parameter is `default` rather than `item`
54
+ - hjq-input-many allows you to provide javascript callbacks.
55
+ input-many fires rapid:add, rapid:change and rapid:remove events
56
+ that can be hooked.
57
+
58
+ You will have to ensure that your hobo-rapid.js and clean.css files
59
+ are updated in your application.
60
+
61
+ === Hobo 0.9.101/0.9.102 (AKA 1.0.BROWN_PAPER_BAG) ===
18
62
 
19
63
  Yes, that was embarrassing. How the test suite failed to catch that
20
64
  one is mind blowing.
data/Rakefile CHANGED
@@ -55,6 +55,8 @@ Jeweler::Tasks.new do |gemspec|
55
55
  gemspec.summary = "The web app builder for Rails"
56
56
  gemspec.homepage = "http://hobocentral.net/"
57
57
  gemspec.authors = ["Tom Locke"]
58
+ gemspec.executables = ['hobo']
59
+ gemspec.default_executable = 'hobo'
58
60
  gemspec.rubyforge_project = "hobo"
59
61
  gemspec.add_dependency("rails", [">= 2.2.2"])
60
62
  gemspec.add_dependency("will_paginate", [">= 2.3.11"])
data/bin/hobo CHANGED
File without changes
@@ -16,7 +16,7 @@ class HoboError < RuntimeError; end
16
16
 
17
17
  module Hobo
18
18
 
19
- VERSION = "0.9.102"
19
+ VERSION = "0.9.103"
20
20
 
21
21
  class PermissionDeniedError < RuntimeError; end
22
22
 
@@ -46,10 +46,13 @@ module Hobo
46
46
  # work around
47
47
  # https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/3510-has_many-build-does-not-set-reverse-reflection
48
48
  # https://hobo.lighthouseapp.com/projects/8324/tickets/447-validation-problems-with-has_many-accessible-true
49
- method = "#{owner.class.reverse_reflection(association_name).name}=".to_sym
50
- record.send(method, owner) if record.respond_to? method
49
+ reverse = owner.class.reverse_reflection(association_name)
50
+ if reverse && reverse.macro==:belongs_to
51
+ method = "#{reverse.name}=".to_sym
52
+ record.send(method, owner) if record.respond_to? method
53
+ end
51
54
  else
52
- owner.include_in_save(association_name, record)
55
+ owner.include_in_save(association_name, record) unless owner.class.reflections[association_name].options[:through]
53
56
  end
54
57
  else
55
58
  # It's already a record
@@ -45,7 +45,6 @@ module Hobo
45
45
  index_options = { :name => options[:index] } unless options[:index] == true
46
46
  index(options[:state_field], index_options || {})
47
47
  end
48
- never_show options[:state_field]
49
48
  attr_protected options[:state_field]
50
49
 
51
50
  unless options[:key_timestamp_field] == false
@@ -100,12 +100,12 @@ module Hobo
100
100
  has_one_without_hobo_permission_check(association_id, options, &extension)
101
101
  reflection = reflections[association_id]
102
102
  if reflection.options[:dependent]==:destroy
103
- #overriding dynamic method created in ActiveRecord::Associations#configure_dependency_for_has_many
103
+ #overriding dynamic method created in ActiveRecord::Associations#configure_dependency_for_has_one
104
104
  method_name = "has_one_dependent_destroy_for_#{reflection.name}".to_sym
105
105
  define_method(method_name) do
106
106
  association = send(reflection.name)
107
107
  unless association.nil?
108
- association.is_a?(Hobo::Model) ? association.user_destroy(active_user) : association.destroy
108
+ association.is_a?(Hobo::Model) ? association.user_destroy(acting_user) : association.destroy
109
109
  end
110
110
  end
111
111
  end
@@ -115,12 +115,12 @@ module Hobo
115
115
  belongs_to_without_hobo_permission_check(association_id, options, &extension)
116
116
  reflection = reflections[association_id]
117
117
  if reflection.options[:dependent]==:destroy
118
- #overriding dynamic method created in ActiveRecord::Associations#configure_dependency_for_has_many
118
+ #overriding dynamic method created in ActiveRecord::Associations#configure_dependency_for_belongs_to
119
119
  method_name = "belongs_to_dependent_destroy_for_#{reflection.name}".to_sym
120
120
  define_method(method_name) do
121
121
  association = send(reflection.name)
122
122
  unless association.nil?
123
- association.is_a?(Hobo::Model) ? association.user_destroy(active_user) : association.destroy
123
+ association.is_a?(Hobo::Model) ? association.user_destroy(acting_user) : association.destroy
124
124
  end
125
125
  end
126
126
  end
@@ -36,7 +36,7 @@ module Hobo
36
36
  child_model = model.reflections[args.first].klass
37
37
  if child_model.view_hints.parent.nil? and !child_model.view_hints.parent_defined
38
38
  parent = model.reverse_reflection(args.first)
39
- child_model.view_hints.parent(parent.name, :undefined => true)
39
+ child_model.view_hints.parent(parent.name, :undefined => true) if parent
40
40
  end
41
41
  args
42
42
  end
@@ -291,7 +291,7 @@ var Hobo = {
291
291
 
292
292
  removeButton: function(el, url, updates, options) {
293
293
  if (options.fade == null) { options.fade = true; }
294
- if (options.confirm == null) { options.fade = "Are you sure?"; }
294
+ if (options.confirm == null) { options.confirm = "Are you sure?"; }
295
295
 
296
296
  if (options.confirm == false || confirm(options.confirm)) {
297
297
  var objEl = Hobo.objectElementFor(el)
@@ -481,6 +481,14 @@ Element.findContaining = function(el, tag) {
481
481
  return null;
482
482
  }
483
483
 
484
+ Element.prototype.childWithClass = function(klass) {
485
+ var ret=null;
486
+ this.childElements().each(function(el2) {
487
+ if(ret==null && el2.hasClassName(klass)) ret=el2;
488
+ });
489
+ return ret;
490
+ }
491
+
484
492
  // Add an afterEnterEditMode hook to in-place-editor
485
493
  origEnterEditMode = Ajax.InPlaceEditor.prototype.enterEditMode
486
494
  Ajax.InPlaceEditor.prototype.enterEditMode = function(evt) {
@@ -519,6 +527,9 @@ HoboBehavior = Class.create({
519
527
  this.mainSelector = mainSelector
520
528
  this.features = features
521
529
  this.addEvents(mainSelector, features.events)
530
+ if (features.initialize) {
531
+ document.observe("dom:loaded", features.initialize);
532
+ }
522
533
  },
523
534
 
524
535
  addEvents: function(parentSelector, events) {
@@ -557,91 +568,154 @@ new HoboBehavior("ul.input-many", {
557
568
  ".remove-item:click": 'removeOne'
558
569
  }
559
570
  },
560
-
561
- addOne: function(ev, el) {
562
- Event.stop(ev)
563
- var ul = el.up('ul'), li = el.up('li')
564
-
565
- var thisItem = li.down('div.input-many-item')
566
- var newItem = "<li style='display:none'><div class='input-many-item'>" +
567
- thisItem.innerHTML +
568
- "</div>" +
569
- "<div class='buttons' />" +
570
- "</div></li>"
571
- var newItem = DOM.Builder.fromHTML(newItem)
572
- ul.appendChild(newItem);
573
- this.clearInputs(newItem);
574
-
575
- this.updateButtons()
576
- this.updateInputNames()
577
-
578
- ul.fire("rapid:add", { element: newItem })
579
- ul.fire("rapid:change", { element: newItem })
571
+
572
+ initialize: function(ul) {
573
+ // disable all elements inside our template, and mark them so we can find them later.
574
+ $$(".input-many-template input:enabled, .input-many-template select:enabled, .input-many-template textarea:enabled, .input-many-template button:enabled").each(function(input) {
575
+ input.disabled = true;
576
+ input.addClassName("input_many_template_input");
577
+ });
578
+ },
579
+
580
+ // given this==the input-many, returns a lambda that updates the name & id for an element
581
+ getNameUpdater: function(new_index) {
582
+ var name_prefix = Hobo.getClassData(this, 'input-many-prefix');
583
+ var id_prefix = name_prefix.replace(/\[/g, "_").replace(/\]/g, "");
584
+ var name_re = RegExp("^" + RegExp.escape(name_prefix)+ "\[\-?[0-9]+\]");
585
+ var name_sub = name_prefix + '[' + new_index.toString() + ']';
586
+ var id_re = RegExp("^" + RegExp.escape(id_prefix)+ "_\-?[0-9]+");
587
+ var id_sub = id_prefix + '_' + new_index.toString();
588
+ var class_re = RegExp(RegExp.escape(name_prefix)+ "\[\-?[0-9]+\]");
589
+ var class_sub = name_sub;
580
590
 
581
- new Effect.BlindDown(newItem, {duration: 0.3})
591
+ return function() {
592
+ if(this.name) {
593
+ this.name = this.name.replace(name_re, name_sub);
594
+ }
595
+ if (id_prefix==this.id.slice(0, id_prefix.length)) {
596
+ this.id = this.id.replace(id_re, id_sub);
597
+ } else {
598
+ // silly rails. text_area_tag and text_field_tag use different conventions for the id.
599
+ if(name_prefix==this.id.slice(0, name_prefix.length)) {
600
+ this.id = this.id.replace(name_re, name_sub);
601
+ } /* else {
602
+ hjq.util.log("hjq.input_many.update_id: id_prefix "+id_prefix+" didn't match input "+this.id);
603
+ } */
604
+ }
605
+ if (class_re.test(this.className)) {
606
+ this.className = this.className.replace(class_re, class_sub);
607
+ }
608
+ return this;
609
+ };
582
610
  },
583
-
584
- removeOne: function(ev, el) {
585
- Event.stop(ev)
586
- var self = this;
587
- var ul = el.up('ul'), li = el.up('li')
588
- if (li.parentNode.childElements().length == 1) {
589
- // It's the last one - don't remove it, just clear it
590
- this.clearInputs(li)
591
- } else {
592
- new Effect.BlindUp(li, { duration: 0.3, afterFinish: function (ef) {
593
- li.remove()
594
- self.updateButtons()
595
- self.updateInputNames()
596
- } });
611
+
612
+ // given this==an input-many item, get the submit index
613
+ getIndex: function() {
614
+ return Number(this.id.match(/\[([0-9])+\]$/)[1]);
615
+ },
616
+
617
+ /* For some reason, select() and down() and all those useful functions aren't working for us. Roll our own replacement. */
618
+ recurse_elements_with_class: function(el, klass, f) {
619
+ var that=this;
620
+ if(klass==null || el.hasClassName(klass)) {
621
+ f(el);
597
622
  }
598
- ul.fire("rapid:remove")
599
- ul.fire("rapid:change")
623
+ el.childElements().each(function(el2) {that.recurse_elements_with_class.call(that, el2, klass, f);});
600
624
  },
601
625
 
602
-
603
- clearInputs: function(item) {
604
- $(item).select('input,select,textarea').each(function(input){
605
- t = input.getAttribute('type')
606
- if (t && t.match(/hidden/i)) {
607
- input.remove()
608
- } else {
609
- input.value = ""
626
+ addOne: function(ev, el) {
627
+ Event.stop(ev);
628
+ var ul = el.up('ul.input-many'), li = el.up('li.input-many-li');
629
+
630
+ var template = ul.down("li.input-many-template");
631
+ var clone = $(template.cloneNode(true));
632
+ clone.removeClassName("input-many-template");
633
+ // length-2 because ignore the template li and the empty li
634
+ var name_updater = this.getNameUpdater.call(ul, ul.childElements().length-2);
635
+
636
+ function reenable_inputs(el) {
637
+ if(el.hasClassName("input_many_template_input")) {
638
+ el.disabled = false;
639
+ el.removeClassName("input_many_template_input");
610
640
  }
611
- })
641
+ el.childElements().each(function(el2) {
642
+ if(!el2.hasClassName("input-many-template")) reenable_inputs(el2);
643
+ });
644
+ }
645
+ reenable_inputs(clone);
646
+
647
+ // update id & name
648
+ this.recurse_elements_with_class.call(this, clone, null, function(el) {
649
+ name_updater.call(el);
650
+ });
651
+
652
+ // do the add with anim
653
+ clone.setStyle("display", "none")
654
+ li.insert({after: clone});
655
+ new Effect.BlindDown(clone, {duration: 0.3})
656
+
657
+ // visibility
658
+ if(li.hasClassName("empty")) {
659
+ li.addClassName("hidden");
660
+ li.childWithClass("empty-input").disabled = true;
661
+ } else {
662
+ // now that we've added an element after us, we should only have a '-' button
663
+ li.childWithClass("buttons").childWithClass("remove-item").removeClassName("hidden");
664
+ li.childWithClass("buttons").childWithClass("add-item").addClassName("hidden");
665
+ }
666
+
667
+ Event.addBehavior.reload();
668
+
669
+ ul.fire("rapid:add", { element: clone })
670
+ ul.fire("rapid:change", { element: clone })
671
+
672
+ return;
612
673
  },
613
-
614
- updateButtons: function() {
615
- var removeButton = "<button class='remove-item'>-</button>"
616
- var addButton = "<button class='add-item'>+</button>"
617
-
618
- var ul = this.element
619
- var children = ul.childElements();
620
- // assumption: only get here after add or remove, so only second last button needs the "+" removed
621
- if(children.length > 1) {
622
- // cannot use .down() because that's a depth-first search. Did I mention that I hate Prototype?
623
- children[children.length-2].childElements().last().innerHTML = removeButton;
674
+
675
+ removeOne: function(ev, el) {
676
+ Event.stop(ev);
677
+ var that = this;
678
+ var ul = el.up('ul.input-many'), li = el.up('li.input-many-li')
679
+ var minimum = parseInt(Hobo.getClassData(ul, 'minimum'));
680
+
681
+ ul.fire("rapid:remove", { element: li })
682
+
683
+ // rename everybody from me onwards
684
+ var i=this.getIndex.call(li)
685
+ var n=li.next();
686
+ for(; n; i+=1, n=n.next()) {
687
+ var name_updater = this.getNameUpdater.call(ul, i);
688
+ this.recurse_elements_with_class.call(this, n, null, function(el) {name_updater.call(el);});
689
+ }
690
+
691
+ // adjust +/- buttons on the button element as appropriate
692
+ var last=ul.childElements()[ul.childElements().length-1];
693
+ if(last==li) {
694
+ last = last.previous();
624
695
  }
625
- if(children.length > 0) {
626
- children[children.length-1].childElements().last().innerHTML = removeButton + ' ' + addButton;
696
+
697
+ if(last.hasClassName("empty")) {
698
+ last.removeClassName("hidden");
699
+ this.recurse_elements_with_class.call(this, last, "empty-input", function(el) {el.disabled=false;});
700
+ } else {
701
+ // if we've reached the minimum, we don't want to add the '-' button
702
+ if(ul.childElements().length-3 <= minimum||0) {
703
+ last.childWithClass("buttons").childWithClass("remove-item").addClassName("hidden");
704
+ } else {
705
+ last.childWithClass("buttons").childWithClass("remove-item").removeClassName("hidden");
706
+ }
707
+ last.childWithClass("buttons").childWithClass("add-item").removeClassName("hidden");
627
708
  }
628
- Event.addBehavior.reload()
709
+
710
+ new Effect.BlindUp(li, { duration: 0.3, afterFinish: function (ef) {
711
+ li.remove()
712
+ } });
713
+
714
+ ul.fire("rapid:change")
629
715
  },
630
-
631
- updateInputNames: function() {
632
- var prefix = Hobo.getClassData(this.element, 'input-many-prefix')
633
-
634
- this.element.selectChildren('li').each(function(li, index) {
635
- li.select('*[name]').each(function(control) {
636
- if(control.name) {
637
- var changeId = control.id == control.name;
638
- control.name = control.name.sub(new RegExp("^" + RegExp.escape(prefix) + "\[[0-9]+\]"), prefix + '[' + index +']');
639
- if (changeId) control.id = control.name;
640
- }
641
- })
642
- })
643
- }
644
-
716
+
717
+
718
+
645
719
  })
646
720
 
647
721
 
@@ -111,8 +111,6 @@ form .actions input { margin: 0; }
111
111
  color: white;
112
112
  }
113
113
 
114
- .article {margin: 20px 0; border-top: 1px dotted #ccc;}
115
-
116
114
  .field-list th {width: 120px; white-space: nowrap;}
117
115
  .field-list td {width: auto;}
118
116
  .field-list .input-help { color: #888;}
@@ -317,6 +315,7 @@ ul.input-all {list-style-type: none;}
317
315
  ul.input-many > li { overflow:hidden; zoom:1;}
318
316
  ul.input-many .input-many-item {float:left;}
319
317
  ul.input-many div.buttons {float:left; margin-left:10px;}
318
+ li.input-many-template { display:none; }
320
319
 
321
320
  ul.check-many { list-style-type: none; margin-left: 0px;}
322
321
  ul.check-many li input { vertical-align: -20%;}
@@ -19,6 +19,14 @@
19
19
  - skip: render nothing at all. This will omit the entire row (including the label)
20
20
  - ignore: render the input normally. That is, don't even perform the edit check.
21
21
 
22
+ ### Example
23
+
24
+ <field-list fields="first-name, last-name, city">
25
+ <first-name-label:>Given Name</first-name-label:>
26
+ <last-name-label:>Family Name</last-name-label:>
27
+ <city-view:><name-one/></city-view:>
28
+ </field-list>
29
+
22
30
  -->
23
31
  <def tag="field-list" attrs="tag, no-edit">
24
32
  <% tag ||= scope.in_form ? "input" : "view"; no_edit ||= "skip" %>
@@ -606,7 +614,7 @@ The context should be a user object. If `this == current_user` the "you" form is
606
614
  <set user="&Hobo::User.default_user_model"/>
607
615
  <select-menu if="&user && RAILS_ENV == 'development'"
608
616
  first-option="Guest" options="&user.all(:limit => 30).*.login"
609
- onchange="location.href = '/dev/set_current_user?login=' + this.options[this.selectedIndex].value"
617
+ onchange="location.href = '#{dev_support_path}/set_current_user?login=' + this.options[this.selectedIndex].value"
610
618
  selected="#{current_user.login}"
611
619
  class="dev-user-changer"/>
612
620
  </def>
@@ -874,7 +874,7 @@ Use the `uri` option to specify a redirect location:
874
874
 
875
875
  This tag is very different from tags like `<select-many>` and `<check-many>` in that:
876
876
 
877
- - Those tags are used to *chose existing records* to include in the association, while `<input-many>` is used to actually create or edit the records in the association.
877
+ - Those tags are used to *choose existing records* to include in the association, while `<input-many>` is used to actually create or edit the records in the association.
878
878
 
879
879
  ### Example
880
880
 
@@ -890,36 +890,90 @@ The body of the tag will be repeated for each of the current records in the coll
890
890
 
891
891
  - skip: Passed through to the `<field-list>`. If not specified, it defaults to the parent association.
892
892
 
893
+ ### Example
894
+
895
+ Say you are creating a new `Category` in your online shop, and you want to create some initial products *in the same form*, you can add the following to your form:
896
+
897
+ <hjq-input-many:products fields="name, price" />
898
+
899
+ You'll often want to provide the `item` parameter:
900
+
901
+ <hjq-input-many:products><item:><field-list fields="name, price" /></item:></hjq-input-many>
902
+
903
+ A fully worked up example of nested hjq-input-many's may be found in [agility/jquery-test](http://github.com/tablatom/agility/blob/jquery-test/app/views/projects/nested_has_many_test.dryml)
904
+
905
+ ### Attributes
906
+
907
+ - `minimum`: the minimum number of items in the collection. Currently only '0' and '1' are supported values. The default is '0'.
908
+
909
+ - `fields`, `skip`: passed down to the `field-list` tag in the default `item`.
910
+
911
+ - `template`: the default values for new items. Normally this functionality is better provided by Model.new, but it's here if you need it.
912
+
893
913
  -->
894
- <def tag="input-many" attrs="fields,skip" polymorphic>
914
+ <def tag="input-many" attrs="minimum, fields, skip, template, add-hook, remove-hook" polymorphic >
915
+ <%
916
+ # helper function to create id's on buttons to facilitate testing
917
+ def underize(s)
918
+ s.gsub(/\[/,"_").gsub(/\]/,"")
919
+ end
920
+ %>
895
921
  <set empty="&this.empty?"/>
922
+ <% template ||= this.try.new_candidate || this.member_class.new %>
923
+ <% minimum ||= 0 ; minimum = minimum.to_i %>
896
924
  <% 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 %>
897
- <ul class="input-many #{this_field.dasherize} #{css_data :input_many_prefix, param_name_for_this}">
898
- <li repeat class="#{'record-with-errors' unless this.errors.empty?}">
925
+ <ul class="input-many #{this_field.dasherize} #{css_data :input_many_prefix, param_name_for_this} #{css_data(:minimum, minimum)} #{css_data(:add_hook, add_hook) if add_hook} #{css_data(:remove_hook, remove_hook) if remove_hook}">
926
+ <fake-field-context fake-field="-1" context="&template">
927
+ <li class="input-many-li input-many-template" id="#{param_name_for_this}">
928
+ <div class="input-many-item" param="default">
929
+ <field-list param merge-attrs="fields" skip="&skip" />
930
+ </div>
931
+ <div class="buttons">
932
+ <button param="remove-item" id="#{underize param_name_for_this}_remove">-</button>
933
+ <button param="add-item" id="#{underize param_name_for_this}_add">+</button>
934
+ </div>
935
+ </li>
936
+ </fake-field-context>
937
+ <li class="input-many-li empty #{'hidden' unless this.empty? and minimum==0}" id="#{param_name_for_this}[-1]_empty">
938
+ <!-- HACK way to signal an empty collection to the controller -->
939
+ <input type="hidden" class="empty-input" id="#{param_name_for_this}" name="#{param_name_for_this}" value="" disabled="&(!this.empty? || minimum>0)" />
940
+ <fake-field-context fake-field="-1" context="&template">
941
+ <div param="empty-message">
942
+ <ht key="#{this.class.class_name.tableize}.collection.empty_message">
943
+ No <%= this.class.class_name.titleize.downcase.pluralize %>.
944
+ </ht>
945
+ </div>
946
+ <div class="buttons">
947
+ <button param="remove-item" class="hidden" id="#{underize param_name_for_this}_remove">-</button>
948
+ <button param="add-item" id="#{underize param_name_for_this}_add">+</button>
949
+ </div>
950
+ </fake-field-context>
951
+ </li>
952
+ <fake-field-context fake-field="0" context="&template">
953
+ <li class="input-many-li" if="&(this_parent.empty? && minimum>0)" id="#{param_name_for_this}">
954
+ <div class="input-many-item" param="default">
955
+ <field-list param merge-attrs="fields" skip="&skip" />
956
+ </div>
957
+ <div class="buttons">
958
+ <button param="remove-item" class="hidden" id="#{underize param_name_for_this}_remove">-</button>
959
+ <button param="add-item" id="#{underize param_name_for_this}_add">+</button>
960
+ </div>
961
+ </li>
962
+ </fake-field-context>
963
+ <li repeat class="input-many-li #{'record-with-errors' unless this.errors.empty?}" id="#{param_name_for_this}">
899
964
  <error-messages without-heading class="sub-record"/>
900
965
  <hidden-id-field/>
901
966
  <div class="input-many-item" param="default">
902
- <field-list merge-attrs="fields" skip="&skip"/>
967
+ <field-list param merge-attrs="fields" skip="&skip" />
903
968
  </div>
904
969
  <div class="buttons">
905
- <button type="button" class="remove-item" merge-attrs="disabled" param="remove-item">-</button>
906
- <button type="button" class="add-item" if="&last_item?" merge-attrs="disabled" param="add-item">+</button>
907
- </div>
908
- </li>
909
- <li if="&empty">
910
- <fake-field-context fake-field="0" context="&this.try.new_candidate || this.member_class.new">
911
- <div class="input-many-item" param="default">
912
- <field-list merge-attrs="fields" skip="&skip"/>
913
- </div>
914
- </fake-field-context>
915
- <div class="buttons">
916
- <button type="button" class="add-item" merge-attrs="disabled" param="add-item">+</button>
970
+ <button param="remove-item" class="#{'hidden' if this_parent.length<=minimum}" id="#{underize param_name_for_this}_remove">-</button>
971
+ <button param="add-item" class="#{'hidden' if not last_item?}" id="#{underize param_name_for_this}_add">+</button>
917
972
  </div>
918
973
  </li>
919
974
  </ul>
920
975
  </def>
921
976
 
922
-
923
977
  <!-- Renders a sub-section of a form with fields for every record in a `has_many` association. This is similar to `<input-many>` except there is no ability to add and remove items (i.e. no (+) and (-) buttons).
924
978
  -->
925
979
  <def tag="input-all">
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hobo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.102
4
+ version: 0.9.103
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Locke
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-01 00:00:00 -05:00
12
+ date: 2009-12-10 00:00:00 -05:00
13
13
  default_executable: hobo
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -40,7 +40,7 @@ dependencies:
40
40
  requirements:
41
41
  - - "="
42
42
  - !ruby/object:Gem::Version
43
- version: 0.9.102
43
+ version: 0.9.103
44
44
  version:
45
45
  - !ruby/object:Gem::Dependency
46
46
  name: hobofields
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - "="
52
52
  - !ruby/object:Gem::Version
53
- version: 0.9.102
53
+ version: 0.9.103
54
54
  version:
55
55
  description:
56
56
  email: tom@tomlocke.com
@@ -58,9 +58,8 @@ executables:
58
58
  - hobo
59
59
  extensions: []
60
60
 
61
- extra_rdoc_files:
62
- - LICENSE.txt
63
- - README
61
+ extra_rdoc_files: []
62
+
64
63
  files:
65
64
  - CHANGES.txt
66
65
  - LICENSE.txt