Skip to content

Commit cb5fa96

Browse files
committed
Reworked client code and JS API
1 parent 638d942 commit cb5fa96

File tree

14 files changed

+228
-129
lines changed

14 files changed

+228
-129
lines changed

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ Active Form comes with two helpers to deal with this functionality:
242242
1. `link_to_add_association` will display a link that renders fields to create a new object.
243243
2. `link_to_remove_association` will display a link to remove a existing/dynamic object.
244244

245-
In order to use it you have to insert this line: `//= require link_helpers` to your `app/assets/javascript/application.js` file.
245+
In order to use it you have to insert this line: `//= require activeform` to your `app/assets/javascript/application.js` file.
246246

247247
In our `ConferenceForm` we can dynamically create/remove `Speaker` objects. To do that we would write in the `app/views/conferences/_form.html.erb` partial:
248248

@@ -271,12 +271,16 @@ In our `ConferenceForm` we can dynamically create/remove `Speaker` objects. To d
271271
</div>
272272
273273
<h2>Speaker Details</h2>
274-
<%= f.fields_for :speakers do |speaker_fields| %>
275-
<%= render "speaker_fields", :f => speaker_fields %>
276-
<% end %>
274+
275+
<%= render_association_template f, :speakers %>
276+
<div class="speakers_list">
277+
<%= f.fields_for :speakers do |speaker_fields| %>
278+
<%= render "speaker_fields", :f => speaker_fields %>
279+
<% end %>
280+
</div>
277281
278282
<div class="links">
279-
<%= link_to_add_association "Add a Speaker", f, :speakers %>
283+
<%= link_to_add_association "Add a Speaker", f, :speakers, data: { "association-insertion-node" => ".speakers_list" } %>
280284
</div>
281285
282286
<div class="actions">

app/assets/javascripts/activeform.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
(function($) {
2+
// Form API, draft version:
3+
// $(".task_list").addFormField("tasks")
4+
// $(".task_list").removeFormField("tasks", 2) // destroy second field
5+
6+
var activeFormHelpers = {
7+
generateResourceId: function() {
8+
return new Date().getTime();
9+
}
10+
};
11+
12+
$.fn.removeFormField = function(nodeToDeleteOrIndex, options) {
13+
if(options == undefined) {
14+
options = {}
15+
}
16+
17+
var nodeToDelete;
18+
19+
if(nodeToDeleteOrIndex instanceof jQuery) {
20+
nodeToDelete = nodeToDeleteOrIndex;
21+
} else {
22+
var wrapperClass = this.data('wrapper-class') || 'nested-fields';
23+
console.log(wrapperClass)
24+
nodeToDelete = this.find("." + wrapperClass).filter(function(i, b) {
25+
return $(b).css('display') != 'none';
26+
}).eq(nodeToDeleteOrIndex);
27+
}
28+
29+
this.trigger('before-remove', [nodeToDelete]);
30+
31+
var timeout = this.data('remove-timeout') || 0;
32+
33+
var input = nodeToDelete.find("input").filter(function(i, t) {
34+
return false
35+
})
36+
37+
var isDynamic = nodeToDelete.hasClass("dynamic");
38+
39+
var context = this;
40+
setTimeout(function() {
41+
if (isDynamic) {
42+
nodeToDelete.remove();
43+
} else {
44+
nodeToDelete.find("input[type=hidden]").val("1");
45+
nodeToDelete.hide();
46+
}
47+
context.trigger('after-remove', [nodeToDelete]);
48+
}, timeout);
49+
50+
return this
51+
};
52+
53+
$.fn.addFormField = function(assoc, options) {
54+
if(options == undefined) {
55+
options = {}
56+
}
57+
var templateSelector = "." + assoc + "_template";
58+
59+
var templateContainer;
60+
$(this).parents().each(function(i, el){
61+
var found = $(el).find(templateSelector)
62+
if(found.length > 0) {
63+
templateContainer = found.eq(0)
64+
return false;
65+
}
66+
})
67+
68+
var newId = activeFormHelpers.generateResourceId();
69+
var regex = new RegExp("new_" + assoc, "g");
70+
var content = templateContainer.html().replace(regex, newId);
71+
var contentNode = $(content);
72+
73+
contentNode.addClass("dynamic")
74+
75+
if(!options.insertionMethod) {
76+
options.insertionMethod = 'append';
77+
}
78+
79+
this.trigger('before-insert', [contentNode]);
80+
81+
var addedContent = this[options.insertionMethod](contentNode);
82+
83+
this.trigger('after-insert', [contentNode]);
84+
85+
return this;
86+
};
87+
88+
$(document).on('click', '.add_fields', function(e) {
89+
e.preventDefault();
90+
91+
var $link = $(this);
92+
var form = $link.parents("form").eq(0);
93+
var assoc = $link.data('association');
94+
var insertionMethod = $link.data('association-insertion-method') || $link.data('association-insertion-position');
95+
var insertionNode = $link.data('association-insertion-node');
96+
var insertionTraversal = $link.data('association-insertion-traversal');
97+
98+
if (insertionNode){
99+
if (insertionTraversal){
100+
insertionNode = $link[insertionTraversal](insertionNode);
101+
} else {
102+
if(insertionNode == "this") {
103+
insertionNode = $(this)
104+
} else {
105+
$(this).parents().each(function(i, el){
106+
var found = $(el).find(insertionNode)
107+
if(found.length > 0) {
108+
insertionNode = found.eq(0)
109+
return false;
110+
}
111+
})
112+
}
113+
}
114+
} else {
115+
insertionNode = $link.parent();
116+
}
117+
118+
insertionNode.addFormField(assoc, {
119+
insertionMethod: insertionMethod,
120+
insertionTraversal: insertionTraversal,
121+
})
122+
});
123+
124+
$(document).on('click', '.remove_fields, .remove_fields', function(e) {
125+
e.preventDefault();
126+
127+
var $link = $(this);
128+
var wrapperClass = $link.data('wrapper-class') || 'nested-fields';
129+
var nodeToDelete = $link.closest('.' + wrapperClass);
130+
131+
var triggerNode = nodeToDelete.parent();
132+
133+
triggerNode.removeFormField(nodeToDelete)
134+
});
135+
136+
})(jQuery);

app/assets/javascripts/link_helpers.js

Lines changed: 0 additions & 61 deletions
This file was deleted.

lib/active_form.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Engine < ::Rails::Engine
1010

1111
config.before_initialize do
1212
if config.action_view.javascript_expansions
13-
config.action_view.javascript_expansions[:link_helpers] = %w(link_helpers)
13+
config.action_view.javascript_expansions[:activeform] = %w(activeform)
1414
end
1515
end
1616

lib/active_form/view_helpers.rb

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def link_to_remove_association(name, f, html_options={})
77

88
is_existing = f.object.persisted?
99
classes << (is_existing ? 'existing' : 'dynamic')
10-
10+
1111
wrapper_class = html_options.delete(:wrapper_class)
1212
html_options[:class] = [html_options[:class], classes.join(' ')].compact.join(' ')
1313
html_options[:'data-wrapper-class'] = wrapper_class if wrapper_class.present?
@@ -21,35 +21,38 @@ def link_to_remove_association(name, f, html_options={})
2121

2222
def render_association(association, f, new_object, render_options={}, custom_partial=nil)
2323
partial = get_partial_path(custom_partial, association)
24-
24+
2525
if f.respond_to?(:semantic_fields_for)
2626
method_name = :semantic_fields_for
2727
elsif f.respond_to?(:simple_fields_for)
2828
method_name = :simple_fields_for
2929
else
3030
method_name = :fields_for
3131
end
32-
32+
3333
f.send(method_name, association, new_object, {:child_index => "new_#{association}"}.merge(render_options)) do |builder|
3434
render(partial: partial, locals: {:f => builder})
3535
end
3636
end
3737

3838
def link_to_add_association(name, f, association, html_options={})
39-
render_options = html_options.delete(:render_options)
40-
render_options ||= {}
41-
override_partial = html_options.delete(:partial)
42-
4339
html_options[:class] = [html_options[:class], "add_fields"].compact.join(' ')
4440
html_options[:'data-association'] = association.to_s
4541

42+
link_to(name, '#', html_options)
43+
end
44+
45+
def render_association_template(f, association, html_options={})
46+
render_options = html_options.delete(:render_options)
47+
render_options ||= {}
48+
override_partial = html_options.delete(:partial)
4649
new_object = create_object(f, association)
4750

48-
html_options[:'data-association-insertion-template'] = CGI.escapeHTML(render_association(association, f, new_object, render_options, override_partial).to_str).html_safe
49-
50-
link_to(name, '#', html_options)
51+
content_tag :template, class: "association_template #{association}_template" do
52+
render_association(association, f, new_object, render_options, override_partial)
53+
end
5154
end
52-
55+
5356
def create_object(f, association)
5457
f.object.get_model(association)
5558
end

lib/rails/generators/form/form_install_generator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def create_forms_test_directory
2020
end
2121

2222
def include_js_file
23-
insert_into_file "app/assets/javascripts/application.js", "//= require link_helpers", :before => "//= require_tree ."
23+
insert_into_file "app/assets/javascripts/application.js", "//= require activeform", :before => "//= require_tree ."
2424
end
2525
end
2626
end

test/dummy/app/assets/javascripts/application.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
//
1313
//= require jquery
1414
//= require jquery_ujs
15-
//= require link_helpers
15+
//= require activeform
1616
//= require_tree .

test/dummy/app/assets/javascripts/projects.js

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,20 @@ $(document).ready(function() {
2323
$(".project-tag-fields a.add_fields").
2424
data("association-insertion-position", 'before').
2525
data("association-insertion-node", 'this');
26-
$('.project-tag-fields').bind('after-insert',
27-
function() {
28-
$(this).children("#tag_from_list").remove();
29-
$(this).children("a.add_fields").hide();
30-
});
3126
});
3227

28+
$("body").on('after-insert', '.project-tag-fields',
29+
function() {
30+
$(this).children("#tag_from_list").remove();
31+
$(this).children("a.add_fields").hide();
32+
});
33+
3334
$('#tasks').bind('before-insert', function(e,task_to_be_added) {
3435
task_to_be_added.fadeIn('slow');
3536
});
3637

37-
$('#tasks').bind('after-insert', function(e, added_task) {
38-
//added_task.css("background","red");
39-
});
40-
4138
$('#tasks').bind('before-remove', function(e, task) {
4239
$(this).data('remove-timeout', 1000);
4340
task.fadeOut('slow');
4441
})
45-
46-
$('body').tabs();
47-
});
42+
});

test/dummy/app/views/assignments/_form.html.erb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,22 @@
1717
</div>
1818

1919
<h2>Tasks Details</h2>
20-
<%= f.fields_for :tasks do |task_fields| %>
21-
<%= render "task_fields", :f => task_fields %>
22-
<% end %>
20+
21+
<%= render_association_template(f, :tasks) %>
22+
<div class="task_list">
23+
<%= f.fields_for :tasks do |task_fields| %>
24+
<%= render "task_fields", :f => task_fields %>
25+
<% end %>
26+
</div>
2327

2428
<div class="links">
25-
<%= link_to_add_association "Add a Task", f, :tasks %>
29+
<%= link_to_add_association "Add a Task", f, :tasks, { data: {"association-insertion-node" => ".task_list"} } %>
2630
</div>
2731

32+
<!-- $(".task_list").addFormField("tasks") -->
33+
<!-- $(".task_list").removeFormField("tasks", 2) -->
34+
2835
<div class="actions">
2936
<%= f.submit %>
3037
</div>
31-
<% end %>
38+
<% end %>

test/dummy/app/views/assignments/_task_fields.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
<%= f.text_field :name %>
55
<%= link_to_remove_association "Delete", f %>
66
</div>
7-
</div>
7+
</div>

0 commit comments

Comments
 (0)