cocooned 1.3.0
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.
- 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
|