cocooned 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +194 -0
- data/History.md +253 -0
- data/LICENSE +13 -0
- data/README.md +293 -0
- data/Rakefile +44 -0
- data/app/assets/javascripts/cocoon.js +14 -0
- data/app/assets/javascripts/cocooned/core.js +284 -0
- data/app/assets/javascripts/cocooned/jquery/onload.js +10 -0
- data/app/assets/javascripts/cocooned/jquery/plugin.js +20 -0
- data/app/assets/javascripts/cocooned/plugins/limit.js +22 -0
- data/app/assets/javascripts/cocooned/plugins/reorderable.js +101 -0
- data/app/assets/javascripts/cocooned.js +3 -0
- data/app/assets/stylesheets/cocoon.css +3 -0
- data/app/assets/stylesheets/cocooned.css +9 -0
- data/cocooned.gemspec +37 -0
- data/config/linters/js.json +50 -0
- data/config/linters/ruby.yml +16 -0
- data/gemfiles/Gemfile.rails-4 +8 -0
- data/gemfiles/Gemfile.rails-5 +8 -0
- data/lib/cocooned/association_builder.rb +69 -0
- data/lib/cocooned/helpers/cocoon_compatibility.rb +27 -0
- data/lib/cocooned/helpers/deprecate.rb +49 -0
- data/lib/cocooned/helpers.rb +331 -0
- data/lib/cocooned/railtie.rb +14 -0
- data/lib/cocooned/version.rb +5 -0
- data/lib/cocooned.rb +6 -0
- data/package.json +24 -0
- data/yarn.lock +1052 -0
- metadata +183 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
Cocooned.Plugins.Reorderable = {
|
2
|
+
|
3
|
+
defaultOptionValue: true,
|
4
|
+
|
5
|
+
bindReorderable: function() {
|
6
|
+
var self = this;
|
7
|
+
|
8
|
+
// Maintain indexes
|
9
|
+
this.container
|
10
|
+
.on('cocooned:after-insert', function(e) { self.reindex(); })
|
11
|
+
.on('cocooned:after-remove', function(e) { self.reindex(); })
|
12
|
+
.on('cocooned:after-move', function(e) { self.reindex(); });
|
13
|
+
|
14
|
+
// Move items
|
15
|
+
this.container.on(
|
16
|
+
this.namespacedNativeEvents('click'),
|
17
|
+
[this.selector('up'), this.selector('down')].join(', '),
|
18
|
+
function(e) {
|
19
|
+
e.preventDefault();
|
20
|
+
var node = this;
|
21
|
+
var up = self.classes['up'].some(function(klass) {
|
22
|
+
return node.className.indexOf(klass) != -1;
|
23
|
+
});
|
24
|
+
self.move(this, up ? 'up' : 'down');
|
25
|
+
});
|
26
|
+
|
27
|
+
// Ensure positions are unique before save
|
28
|
+
this.container.closest('form').on(
|
29
|
+
this.namespacedNativeEvents('submit'),
|
30
|
+
function(e) {
|
31
|
+
self.reindex();
|
32
|
+
});
|
33
|
+
},
|
34
|
+
|
35
|
+
move: function(moveLink, direction) {
|
36
|
+
var self = this;
|
37
|
+
var $mover = $(moveLink);
|
38
|
+
var node = $mover.closest(this.selector('item'));
|
39
|
+
var siblings = (direction == 'up'
|
40
|
+
? node.prevAll(this.selector('item', '&:eq(0)'))
|
41
|
+
: node.nextAll(this.selector('item', '&:eq(0)')));
|
42
|
+
|
43
|
+
if (siblings.length == 0) {
|
44
|
+
return;
|
45
|
+
}
|
46
|
+
|
47
|
+
// Move can be prevented through a 'cocooned:before-move' event handler
|
48
|
+
var eventData = { link: $mover, node: node, cocooned: this };
|
49
|
+
if (!self.notify(node, 'before-move', eventData)) {
|
50
|
+
return false;
|
51
|
+
}
|
52
|
+
|
53
|
+
var height = self.container.outerHeight();
|
54
|
+
var width = self.container.outerWidth();
|
55
|
+
|
56
|
+
self.container.css('height', height).css('width', width);
|
57
|
+
self.hide(node, function() {
|
58
|
+
var movedNode = $(this).detach();
|
59
|
+
movedNode[(direction == 'up' ? 'insertBefore' : 'insertAfter')](siblings);
|
60
|
+
|
61
|
+
self.show(movedNode, function() {
|
62
|
+
self.container.css('height', '').css('width', ''); // Object notation does not work here.
|
63
|
+
self.notify(movedNode, 'after-move', eventData);
|
64
|
+
});
|
65
|
+
});
|
66
|
+
},
|
67
|
+
|
68
|
+
reindex: function() {
|
69
|
+
var self = this;
|
70
|
+
var i = 0;
|
71
|
+
var nodes = this.getItems('&:visible');
|
72
|
+
var eventData = { link: null, nodes: nodes, cocooned: this };
|
73
|
+
|
74
|
+
// Reindex can be prevented through a 'cocooned:before-reindex' event handler
|
75
|
+
if (!this.notify(this.container, 'before-reindex', eventData)) {
|
76
|
+
return false;
|
77
|
+
}
|
78
|
+
|
79
|
+
nodes.each(function() { $('input[id$=_position]', this).val(++i); });
|
80
|
+
this.notify(this.container, 'after-reindex', eventData);
|
81
|
+
},
|
82
|
+
|
83
|
+
show: function(node, callback) {
|
84
|
+
callback = callback || function() {};
|
85
|
+
|
86
|
+
node.addClass('cocooned-visible-item');
|
87
|
+
setTimeout(function() {
|
88
|
+
callback.apply(node);
|
89
|
+
node.removeClass('cocooned-hidden-item');
|
90
|
+
}, 500);
|
91
|
+
},
|
92
|
+
|
93
|
+
hide: function(node, callback) {
|
94
|
+
node.removeClass('cocooned-visible-item').addClass('cocooned-hidden-item');
|
95
|
+
if (callback) {
|
96
|
+
setTimeout(function() {
|
97
|
+
callback.apply(node);
|
98
|
+
}, 500);
|
99
|
+
}
|
100
|
+
}
|
101
|
+
};
|
data/cocooned.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'cocooned/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'cocooned'
|
9
|
+
spec.version = Cocooned::VERSION
|
10
|
+
spec.licenses = ['Apache-2.0']
|
11
|
+
spec.authors = ['Gaël-Ian Havard', 'Nathan Van der Auwera']
|
12
|
+
spec.email = ['gael-ian@notus.sh', 'nathan@dixis.com']
|
13
|
+
|
14
|
+
spec.summary = 'Unobtrusive nested forms handling using jQuery.'
|
15
|
+
spec.description = 'Easier nested form. Supports standard Rails forms, Formtastic and SimpleForm.'
|
16
|
+
spec.homepage = 'http://github.com/notus-sh/cocooned'
|
17
|
+
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
20
|
+
else
|
21
|
+
raise 'RubyGems 2.0 or newer is required.'
|
22
|
+
end
|
23
|
+
|
24
|
+
spec.require_paths = ['lib']
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{^(test|spec|features)/})
|
27
|
+
end
|
28
|
+
|
29
|
+
spec.add_dependency 'rails', '>= 4.0', '<= 6.0'
|
30
|
+
|
31
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
32
|
+
spec.add_development_dependency 'jasmine', '~> 3.2'
|
33
|
+
spec.add_development_dependency 'rake'
|
34
|
+
spec.add_development_dependency 'rspec', '~> 3.8.0'
|
35
|
+
spec.add_development_dependency 'rspec-rails', '~> 3.8.0'
|
36
|
+
spec.add_development_dependency 'rubocop'
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
{
|
2
|
+
"extends": "standard",
|
3
|
+
"env": {
|
4
|
+
"browser": true,
|
5
|
+
"jquery": true
|
6
|
+
},
|
7
|
+
"parserOptions": { "ecmaVersion": 5 },
|
8
|
+
"rules": {
|
9
|
+
// Force strict mode
|
10
|
+
"strict": ["error", "function"],
|
11
|
+
|
12
|
+
// Coding style
|
13
|
+
"semi": ["error", "always"], // Keep semi-colon, even if standard say we shouldn't
|
14
|
+
"semi-style": ["error", "last"],
|
15
|
+
"object-curly-newline": ["error", { "consistent": true }],
|
16
|
+
"object-curly-spacing": ["error", "always"],
|
17
|
+
"switch-colon-spacing": ["error"],
|
18
|
+
"linebreak-style": ["error"],
|
19
|
+
|
20
|
+
// Warn on code smells
|
21
|
+
"max-len": ["warn", { "code": 150 }],
|
22
|
+
"max-params": ["warn"],
|
23
|
+
|
24
|
+
// No dead code
|
25
|
+
"no-else-return": ["error"],
|
26
|
+
"no-empty-function": ["error"],
|
27
|
+
"no-empty-pattern": ["error"],
|
28
|
+
"no-unused-expressions": ["error"],
|
29
|
+
"no-lonely-if": ["error"],
|
30
|
+
|
31
|
+
// Prevent stupid errors
|
32
|
+
"array-callback-return": ["error"],
|
33
|
+
"guard-for-in": ["error"],
|
34
|
+
"no-invalid-this": ["error"],
|
35
|
+
"no-loop-func": ["error"],
|
36
|
+
"radix": ["error"],
|
37
|
+
"no-catch-shadow": ["error"],
|
38
|
+
"no-use-before-define": ["error"],
|
39
|
+
|
40
|
+
// Prevent stupid syntaxes
|
41
|
+
"no-useless-concat": ["error"],
|
42
|
+
"no-useless-return": ["error"],
|
43
|
+
|
44
|
+
// No debug code will be commited
|
45
|
+
"no-alert": ["error"],
|
46
|
+
|
47
|
+
// eslint don't know the context
|
48
|
+
"no-invalid-this": ["off"]
|
49
|
+
}
|
50
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
inherit_mode:
|
2
|
+
merge:
|
3
|
+
- Exclude
|
4
|
+
|
5
|
+
AllCops:
|
6
|
+
Exclude:
|
7
|
+
- 'spec/dummy/bin/*'
|
8
|
+
|
9
|
+
Metrics/LineLength:
|
10
|
+
Max: 150
|
11
|
+
|
12
|
+
Metrics/BlockLength:
|
13
|
+
Exclude:
|
14
|
+
- 'spec/dummy/db/schema.rb'
|
15
|
+
- 'spec/cocooned/**/*_spec.rb'
|
16
|
+
- 'spec/shared_examples/**/*.rb'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocooned
|
4
|
+
class AssociationBuilder
|
5
|
+
attr_accessor :association
|
6
|
+
attr_accessor :form
|
7
|
+
attr_accessor :options
|
8
|
+
|
9
|
+
def initialize(form, association, options = {})
|
10
|
+
self.form = form
|
11
|
+
self.association = association
|
12
|
+
self.options = options.reverse_merge(force_non_association_create: false, wrap_object: false)
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_object
|
16
|
+
model = reflection ? build_with_reflection : build_without_reflection
|
17
|
+
model = @options[:wrap_object].call(model) if @options[:wrap_object].respond_to?(:call)
|
18
|
+
model
|
19
|
+
end
|
20
|
+
|
21
|
+
def singular_name
|
22
|
+
association.to_s.singularize
|
23
|
+
end
|
24
|
+
|
25
|
+
def plural_name
|
26
|
+
association.to_s.pluralize
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def reflection
|
32
|
+
@reflection ||= begin
|
33
|
+
klass = form.object.class
|
34
|
+
klass.respond_to?(:reflect_on_association) ? klass.reflect_on_association(association) : nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_with_reflection
|
39
|
+
return build_with_conditions if should_use_conditions?
|
40
|
+
|
41
|
+
# Assume ActiveRecord or compatible
|
42
|
+
# We use a clone of the current form object to not link
|
43
|
+
# object together (even if unsaved)
|
44
|
+
dummy = form.object.dup
|
45
|
+
model = if reflection.collection?
|
46
|
+
dummy.send(association).build
|
47
|
+
else
|
48
|
+
dummy.send("build_#{association}")
|
49
|
+
end
|
50
|
+
model = model.dup if model.frozen?
|
51
|
+
model
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_without_reflection
|
55
|
+
methods = %W[build_#{plural_name} build_#{singular_name}].select { |m| form.object.respond_to?(m) }
|
56
|
+
raise "Association #{association} doesn't exist on #{form.object.class}" unless methods.any?
|
57
|
+
form.object.send(methods.first)
|
58
|
+
end
|
59
|
+
|
60
|
+
def should_use_conditions?
|
61
|
+
reflection.class.name == 'Mongoid::Relations::Metadata' || @options[:force_non_association_create]
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_with_conditions
|
65
|
+
conditions = reflection.respond_to?(:conditions) ? reflection.conditions.flatten : []
|
66
|
+
reflection.klass.new(*conditions)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cocooned/helpers/deprecate'
|
4
|
+
|
5
|
+
module Cocooned
|
6
|
+
module Helpers
|
7
|
+
# Provide aliases to old Cocoon method for backward compatibility.
|
8
|
+
# Cocoon methods are deprecated and will be removed in next major release.
|
9
|
+
#
|
10
|
+
# TODO: Remove in 2.0
|
11
|
+
module CocoonCompatibility
|
12
|
+
extend Cocooned::Helpers::Deprecate
|
13
|
+
|
14
|
+
# @deprecated: Please use {#cocooned_add_item_link} instead
|
15
|
+
def link_to_add_association(*args, &block)
|
16
|
+
cocooned_add_item_link(*args, &block)
|
17
|
+
end
|
18
|
+
deprecate_release :link_to_add_association, :cocooned_add_item_link
|
19
|
+
|
20
|
+
# @deprecated: Please use {#cocooned_remove_item_link} instead
|
21
|
+
def link_to_remove_association(*args, &block)
|
22
|
+
cocooned_remove_item_link(*args, &block)
|
23
|
+
end
|
24
|
+
deprecate_release :link_to_remove_association, :cocooned_remove_item_link
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubygems/deprecate'
|
4
|
+
|
5
|
+
module Cocooned
|
6
|
+
module Helpers
|
7
|
+
# Extend the standard Gem::Deprecation module to add a deprecation
|
8
|
+
# method that specify the gem release where methods will disappear
|
9
|
+
# instead of a date.
|
10
|
+
module Deprecate
|
11
|
+
extend Gem::Deprecate
|
12
|
+
|
13
|
+
def deprecate_release_message(target_and_name, replacement, release = '2.0', location = nil)
|
14
|
+
[
|
15
|
+
"NOTE: #{target_and_name} is deprecated",
|
16
|
+
replacement == :none ? ' with no replacement' : "; use #{replacement} instead",
|
17
|
+
format('. It will dissapear in %s.', release),
|
18
|
+
location.nil? ? '' : "\n#{target_and_name} called from #{location}"
|
19
|
+
].join.strip
|
20
|
+
end
|
21
|
+
|
22
|
+
module_function :deprecate_release_message
|
23
|
+
|
24
|
+
def deprecate_release(name, replacement, release = '2.0')
|
25
|
+
class_eval do
|
26
|
+
old = "_deprecated_#{name}"
|
27
|
+
alias_method old, name
|
28
|
+
define_method name do |*args, &block|
|
29
|
+
klass = is_a? Module
|
30
|
+
target = klass ? "#{self}." : "#{self.class}#"
|
31
|
+
|
32
|
+
unless Gem::Deprecate.skip
|
33
|
+
warn(deprecate_release_message(
|
34
|
+
"#{target}#{name}",
|
35
|
+
replacement,
|
36
|
+
release,
|
37
|
+
Gem.location_of_caller.join(':')
|
38
|
+
))
|
39
|
+
end
|
40
|
+
|
41
|
+
send old, *args, &block
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module_function :deprecate_release
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|