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.
- data/CHANGES.txt +45 -1
- data/Rakefile +2 -0
- data/bin/hobo +0 -0
- data/lib/hobo.rb +1 -1
- data/lib/hobo/accessible_associations.rb +6 -3
- data/lib/hobo/lifecycles.rb +0 -1
- data/lib/hobo/permissions.rb +4 -4
- data/lib/hobo/view_hints.rb +1 -1
- data/rails_generators/hobo_rapid/templates/hobo-rapid.js +150 -76
- data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +1 -2
- data/taglibs/rapid_core.dryml +9 -1
- data/taglibs/rapid_forms.dryml +72 -18
- metadata +6 -7
data/CHANGES.txt
CHANGED
@@ -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.
|
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
|
data/lib/hobo.rb
CHANGED
@@ -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
|
-
|
50
|
-
|
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
|
data/lib/hobo/lifecycles.rb
CHANGED
@@ -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
|
data/lib/hobo/permissions.rb
CHANGED
@@ -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#
|
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(
|
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#
|
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(
|
123
|
+
association.is_a?(Hobo::Model) ? association.user_destroy(acting_user) : association.destroy
|
124
124
|
end
|
125
125
|
end
|
126
126
|
end
|
data/lib/hobo/view_hints.rb
CHANGED
@@ -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.
|
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
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
var
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
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
|
-
|
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
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
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
|
-
|
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
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
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
|
-
|
615
|
-
|
616
|
-
var
|
617
|
-
|
618
|
-
var
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
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
|
-
|
626
|
-
|
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
|
-
|
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
|
-
|
632
|
-
|
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%;}
|
data/taglibs/rapid_core.dryml
CHANGED
@@ -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 = '/
|
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>
|
data/taglibs/rapid_forms.dryml
CHANGED
@@ -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 *
|
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
|
-
<
|
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
|
906
|
-
<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.
|
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-
|
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.
|
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.
|
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
|
-
|
63
|
-
- README
|
61
|
+
extra_rdoc_files: []
|
62
|
+
|
64
63
|
files:
|
65
64
|
- CHANGES.txt
|
66
65
|
- LICENSE.txt
|