hobo 0.9.102 → 0.9.103

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