Skip to content
This repository was archived by the owner on Dec 12, 2021. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions lib/nested_form/builder_mixin.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'nested_form/unresolved_length_validator'

module NestedForm
module BuilderMixin
# Adds a link to insert a new associated records. The first argument is the name of the link, the second is the name of the association.
Expand Down Expand Up @@ -31,6 +33,24 @@ def link_to_add(*args, &block)

options[:class] = [options[:class], "add_nested_fields"].compact.join(" ")
options["data-association"] = association
if Rails.application.config.nested_form.use_length_validators || options[:check_maximum] || options[:maximum]
unless maximum = options.delete(:maximum)
validators = self.object.class.validators_on(association)
length_validators = validators.select {|item| item.is_a? ActiveModel::Validations::LengthValidator}
if length_validators.length == 1
maximum = length_validators[0].options[:maximum]
end
end

force_check_maximum = options.delete :check_maximum

if maximum
options["data-maximum"] = maximum
elsif force_check_maximum
raise UnresolvedLengthValidator, "nested form builder could not find length of #{association}"
end
end

options["data-blueprint-id"] = fields_blueprint_id = fields_blueprint_id_for(association)
args << (options.delete(:href) || "javascript:void(0)")
args << options
Expand Down
7 changes: 7 additions & 0 deletions lib/nested_form/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@ class ActionView::Base
end
end
end

class Railtie < Rails::Railtie
config.nested_form = ActiveSupport::OrderedOptions.new
#set to true for global use of length validators to hide and show
#add and remove buttons
config.nested_form.use_length_validators = false
end
end
4 changes: 4 additions & 0 deletions lib/nested_form/unresolved_length_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module NestedForm
class UnresolvedLengthValidatorError < StandardError
end
end
21 changes: 20 additions & 1 deletion vendor/assets/javascripts/jquery_nested_form.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

// Make the context correct by replacing <parents> with the generated ID
// of each of the parent objects
var context = ($(link).closest('.fields').closestChild('input, textarea, select').eq(0).attr('name') || '').replace(new RegExp('\[[a-z_]+\]$'), '');
var context = ($(link).closest('.fields').closestChild('[name]').eq(0).attr('name') || '').replace(new RegExp('\[[a-z_]+\]$'), '');

// context will be something like this for a brand new form:
// project[tasks_attributes][1255929127459][assignments_attributes][1255929128105]
Expand Down Expand Up @@ -43,6 +43,8 @@
content = $.trim(content.replace(regexp, new_id));

var field = this.insertFields(content, assoc, link);
checkMaximum();

// bubble up event upto document (through form)
field
.trigger({ type: 'nested:fieldAdded', field: field })
Expand All @@ -69,6 +71,8 @@

var field = $link.closest('.fields');
field.hide();

checkMaximum();

field
.trigger({ type: 'nested:fieldRemoved', field: field })
Expand All @@ -81,6 +85,21 @@
$(document)
.delegate('form a.add_nested_fields', 'click', nestedFormEvents.addFields)
.delegate('form a.remove_nested_fields', 'click', nestedFormEvents.removeFields);

// if maximum is set for this nested
function checkMaximum() {
$('form a.add_nested_fields').each(function(){
var maximum = $(this).data('maximum'); // Maximum # of children
if(maximum != null) {
var assoc = $(this).data('association'); // Name of child
var fields_selector = "div.fields :has(input[name*='["+ assoc +"_attributes]']):visible";
fields = $(this).siblings(fields_selector)
fields.length >= maximum ? $(this).hide() : $(this).show();
}
});
}

checkMaximum();
})(jQuery);

// http://plugins.jquery.com/project/closestChild
Expand Down
139 changes: 83 additions & 56 deletions vendor/assets/javascripts/prototype_nested_form.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,90 @@
document.observe('click', function(e, el) {
if (el = e.findElement('form a.add_nested_fields')) {
// Setup
var assoc = el.readAttribute('data-association'); // Name of child
var target = el.readAttribute('data-target');
var blueprint = $(el.readAttribute('data-blueprint-id'));
var content = blueprint.readAttribute('data-blueprint'); // Fields template

// Make the context correct by replacing <parents> with the generated ID
// of each of the parent objects
var context = (el.getOffsetParent('.fields').firstDescendant().readAttribute('name') || '').replace(new RegExp('\[[a-z_]+\]$'), '');

// context will be something like this for a brand new form:
// project[tasks_attributes][1255929127459][assignments_attributes][1255929128105]
// or for an edit form:
// project[tasks_attributes][0][assignments_attributes][1]
if(context) {
var parent_names = context.match(/[a-z_]+_attributes(?=\]\[(new_)?\d+\])/g) || [];
var parent_ids = context.match(/[0-9]+/g) || [];

for(i = 0; i < parent_names.length; i++) {
if(parent_ids[i]) {
content = content.replace(
new RegExp('(_' + parent_names[i] + ')_.+?_', 'g'),
'$1_' + parent_ids[i] + '_');

content = content.replace(
new RegExp('(\\[' + parent_names[i] + '\\])\\[.+?\\]', 'g'),
'$1[' + parent_ids[i] + ']');
(function() {
//equivalent to addFields
document.observe('click', function(e, el) {
if (el = e.findElement('form a.add_nested_fields')) {
// Setup
var assoc = el.readAttribute('data-association'); // Name of child
var target = el.readAttribute('data-target');
var blueprint = $(el.readAttribute('data-blueprint-id'));
var content = blueprint.readAttribute('data-blueprint'); // Fields template

// Make the context correct by replacing <parents> with the generated ID
// of each of the parent objects
var context = (el.getOffsetParent('.fields').firstDescendant().readAttribute('name') || '').replace(new RegExp('\[[a-z_]+\]$'), '');

// context will be something like this for a brand new form:
// project[tasks_attributes][1255929127459][assignments_attributes][1255929128105]
// or for an edit form:
// project[tasks_attributes][0][assignments_attributes][1]
if(context) {
var parent_names = context.match(/[a-z_]+_attributes(?=\]\[(new_)?\d+\])/g) || [];
var parent_ids = context.match(/[0-9]+/g) || [];

for(i = 0; i < parent_names.length; i++) {
if(parent_ids[i]) {
content = content.replace(
new RegExp('(_' + parent_names[i] + ')_.+?_', 'g'),
'$1_' + parent_ids[i] + '_');

content = content.replace(
new RegExp('(\\[' + parent_names[i] + '\\])\\[.+?\\]', 'g'),
'$1[' + parent_ids[i] + ']');
}
}
}
}

// Make a unique ID for the new child
var regexp = new RegExp('new_' + assoc, 'g');
var new_id = new Date().getTime();
content = content.replace(regexp, new_id);
// Make a unique ID for the new child
var regexp = new RegExp('new_' + assoc, 'g');
var new_id = new Date().getTime();
content = content.replace(regexp, new_id);

var field;
if (target) {
field = $$(target)[0].insert(content);
} else {
field = el.insert({ before: content });
checkMaximum();

var field;
if (target) {
field = $$(target)[0].insert(content);
} else {
field = el.insert({ before: content });
}
field.fire('nested:fieldAdded', {field: field});
field.fire('nested:fieldAdded:' + assoc, {field: field});
return false;
}
field.fire('nested:fieldAdded', {field: field});
field.fire('nested:fieldAdded:' + assoc, {field: field});
return false;
}
});

document.observe('click', function(e, el) {
if (el = e.findElement('form a.remove_nested_fields')) {
var hidden_field = el.previous(0),
assoc = el.readAttribute('data-association'); // Name of child to be removed
if(hidden_field) {
hidden_field.value = '1';
});

//equivalent to removeFields
document.observe('click', function(e, el) {
if (el = e.findElement('form a.remove_nested_fields')) {
var hidden_field = el.previous(0),
assoc = el.readAttribute('data-association'); // Name of child to be removed
if(hidden_field) {
hidden_field.value = '1';
}

checkMaximum();

var field = el.up('.fields').hide();
field.fire('nested:fieldRemoved', {field: field});
field.fire('nested:fieldRemoved:' + assoc, {field: field});
return false;
}
var field = el.up('.fields').hide();
field.fire('nested:fieldRemoved', {field: field});
field.fire('nested:fieldRemoved:' + assoc, {field: field});
return false;
});

function checkMaximum() {
var add_buttons = $$('form a.add_nested_fields');
for(var i = 0; i < add_buttons.length; i++) {
var el = add_buttons[i];
var maximum = el.readAttribute('data-maximum'); // Maximum # of children
if(maximum != null) {
var assoc = el.readAttribute('data-association');
var fields_selector = "div.fields :has(input[name*='["+ assoc +"_attributes]']):visible";
var fields = el.siblings().filter(function(sibling) {
return sibling.match(fields_selector);
});
fields.length >= maximum ? el.hide() : el.show();
}
};
}
});

document.observe('dom:loaded', checkMaximum);
})();