dynamic-fields-for 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Sergey Tokarenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,139 @@
1
+ DynamicFieldsFor
2
+ ================
3
+
4
+ DynamicFieldsFor is a Rails plugin which provides the dynamic association fieldsets to your forms without a pain. And it does nothing more.
5
+
6
+ The main features are:
7
+ * Not breaks the HTML layout - no any wrappers, additional divs etc;
8
+ * Works with fields block, i.e. not requires the separated partial for them;
9
+ * Not provides new form helpers, but extend the existing one;
10
+ * Simple and predictable interface and behavior;
11
+ * Not requires any special HTML intities inside templates;
12
+ * Supports [Simple Form](https://github.com/plataformatec/simple_form).
13
+
14
+ ## Alternatives
15
+ * [coccon](https://github.com/nathanvda/cocoon)
16
+ * [Nested Form](https://github.com/ryanb/nested_form)
17
+
18
+ ## Dependencies
19
+ * [rails](https://github.com/rails/rails) >= 3.2.13
20
+ * [jquery-rails](https://github.com/rails/jquery-rails)
21
+ * [association-soft-build](https://github.com/stokarenko/association-soft-build)
22
+
23
+ ## Getting started
24
+
25
+ Add to your Gemfile:
26
+
27
+ ```ruby
28
+ gem 'dynamic-fields-for'
29
+ ```
30
+
31
+ Run the bundle command to install it.
32
+
33
+ Add to `app/assets/javascripts/application.js`:
34
+ ```js
35
+ //= require dynamic-fields-for
36
+ ```
37
+
38
+ ## Usage
39
+ Lets say that we have the models:
40
+
41
+ ```ruby
42
+ class User < ActiveRecord::Base
43
+ has_many :roles
44
+ end
45
+
46
+ class Role < ActiveRecod::Base
47
+ belongs_to :user
48
+
49
+ validates :user, presence: true
50
+ end
51
+ ```
52
+
53
+ First, apply `inverse_of` to User's `:roles` associations, otherwise no chance to pass
54
+ the validation of Role's user presence on user creation:
55
+ ```ruby
56
+ class User < ActiveRecord::Base
57
+ has_many :roles, inverse_of: :user
58
+ end
59
+ ```
60
+
61
+ Add to User model:
62
+ ```ruby
63
+ accepts_nested_attributes_for :roles, allow_destroy: true
64
+ ```
65
+
66
+ Skip `allow_destroy` definition if you don't need to use `remove_fields_link` helper).
67
+
68
+ Take care about strong parameters in controller like that:
69
+ ```ruby
70
+ params.require(:user).permit(roles_attributes: [:id, :_destroy])
71
+ ```
72
+
73
+ It is important to permit `id` role's parameter, don't miss it. As for `_destroy`,
74
+ skip it if you don't need to use `remove_fields_link` helper.
75
+
76
+ Then, in view:
77
+ ```haml
78
+ = form_for resource do |f|
79
+ = f.text_field :user_name
80
+
81
+ = f.fields_for :roles, dynamic: true do |rf|
82
+ = rf.text_field :role_name
83
+ = rf.remove_fields_link 'Remove role'
84
+
85
+ = f.add_fields_link :roles, 'Add role'
86
+
87
+ = f.submit
88
+ ```
89
+
90
+ DynamicFieldsFor supports SimpleForm:
91
+ ```haml
92
+ = simple_form_for resource do |f|
93
+ = f.input :user_name
94
+
95
+ = f.simple_fields_for :roles, dynamic: true do |rf|
96
+ = rf.input :role_name
97
+ = rf.remove_fields_link 'Remove role'
98
+
99
+ = f.add_fields_link :roles, 'Add role'
100
+
101
+ = f.submit
102
+ ```
103
+
104
+ ## JavaScript events
105
+ There are the events which will be triggered on `add_fields_link` click, in actual order:
106
+ * `dynamic-fields:before-add-into` touched to dynamic fields parent node;
107
+ * `dynamic-fields:after-add` touched to each first-level elements which was inserted;
108
+ * `dynamic-fields:after-add-into` touched to dynamic fields parent node;
109
+
110
+ Like that, these events will be triggered on `add_fields_link` click, in actual order:
111
+ * `dynamic-fields:before-remove-from` touched to dynamic fields parent node;
112
+ * `dynamic-fields:before-remove` touched to each first-level elements which going to be removed;
113
+ * `dynamic-fields:after-remove-from` touched to dynamic fields parent node;
114
+
115
+ Typical callback for dynamic fields parent node looks like:
116
+ ```js
117
+ $(document).on('dynamic-fields:after-add-into', function(event){
118
+ $(event.target).find('li').order();
119
+ })
120
+ ```
121
+
122
+ As for first-level elements, need to remember that compatible callbacks
123
+ will be triggered to each of them. To deal with this,
124
+ use `$.find2` javascript helper, which provided by DynamicFieldsFor:
125
+ ```js
126
+ $('#some_id').find2('.some_class');
127
+ // doing the same as...
128
+ $('#some_id').find('.some_class').add($('#some_id').filter('.some_class'));
129
+ ```
130
+
131
+ Typical event callback first-level elements should looks like:
132
+ ```js
133
+ $(document).on('dynamic-fields:after-add', function(event){
134
+ $(event.target).find2('.datepicker').datetimepicker();
135
+ })
136
+ ```
137
+
138
+ ## License
139
+ MIT License. Copyright (c) 2015 Sergey Tokarenko
@@ -0,0 +1,114 @@
1
+ (function() {
2
+ var DynamicFields;
3
+
4
+ $.fn.extend({
5
+ find2: function(selector) {
6
+ return this.filter(selector).add(this.find(selector));
7
+ }
8
+ });
9
+
10
+ DynamicFields = (function() {
11
+ DynamicFields._counter = 0;
12
+
13
+ DynamicFields.add = function(element) {
14
+ var lazy_dynamic_fields;
15
+ lazy_dynamic_fields = this._find($(element).data('dynamicFieldsAdd'));
16
+ if (lazy_dynamic_fields) {
17
+ return lazy_dynamic_fields.add();
18
+ }
19
+ };
20
+
21
+ DynamicFields.remove = function(element) {
22
+ var lazy_dynamic_fields;
23
+ lazy_dynamic_fields = this._find($(element).data('dynamicFieldsRemove'));
24
+ if (lazy_dynamic_fields) {
25
+ return lazy_dynamic_fields.remove($(element));
26
+ }
27
+ };
28
+
29
+ DynamicFields._find = function(fields_id) {
30
+ var $fields_begin, fields_begin;
31
+ $fields_begin = this._$anchor('begin', fields_id);
32
+ fields_begin = $fields_begin.get(0);
33
+ if (!fields_begin) {
34
+ return null;
35
+ }
36
+ return fields_begin.lazy_dynamic_fields || (fields_begin.lazy_dynamic_fields = new this($fields_begin));
37
+ };
38
+
39
+ DynamicFields._anchor = function(postfix, fields_id) {
40
+ return "[data-dynamic-fields-" + postfix + "=" + fields_id + "]";
41
+ };
42
+
43
+ DynamicFields._$anchor = function(postfix, fields_id) {
44
+ return $(this._anchor(postfix, fields_id));
45
+ };
46
+
47
+ function DynamicFields($fields_begin) {
48
+ this.fields_id = $fields_begin.data('dynamicFieldsBegin');
49
+ this.$fields_begin = $fields_begin;
50
+ this.$fields_end = this._$anchor('end');
51
+ this.$fields_parent = this.$fields_begin.parent();
52
+ }
53
+
54
+ DynamicFields.prototype.add = function() {
55
+ var fields_add;
56
+ fields_add = $(this._render_insertion('Add'));
57
+ this.$fields_parent.trigger('dynamic-fields:before-add-into');
58
+ this.$fields_end.before(fields_add);
59
+ fields_add.not('script').trigger('dynamic-fields:after-add');
60
+ return this.$fields_parent.trigger('dynamic-fields:after-add-into');
61
+ };
62
+
63
+ DynamicFields.prototype.remove = function($element) {
64
+ var destroy_inputs, fields_item_begin, fields_remove, node, object_id;
65
+ node = $element;
66
+ while ((fields_item_begin = node.prevAll((this._anchor('item-begin')) + ":first"), fields_item_begin.length === 0)) {
67
+ node = node.parent();
68
+ if (node.length === 0) {
69
+ break;
70
+ }
71
+ }
72
+ if (fields_item_begin.length === 0) {
73
+ return;
74
+ }
75
+ if ((object_id = $element.data('dynamicFieldsRemoveId'))) {
76
+ destroy_inputs = this._render_insertion('Remove').replace(/(["])dynamic_fields_object_id(["])/g, "$1" + object_id + "$2");
77
+ this.$fields_begin.after(destroy_inputs);
78
+ }
79
+ fields_remove = fields_item_begin.nextUntil((this._anchor('item-begin')) + ", " + (this._anchor('end'))).andSelf();
80
+ this.$fields_parent.trigger('dynamic-fields:before-remove-from');
81
+ fields_remove.not('script').trigger('dynamic-fields:before-remove');
82
+ fields_remove.remove();
83
+ return this.$fields_parent.trigger('dynamic-fields:after-remove-from');
84
+ };
85
+
86
+ DynamicFields.prototype._anchor = function(postfix) {
87
+ return this.constructor._anchor(postfix, this.fields_id);
88
+ };
89
+
90
+ DynamicFields.prototype._$anchor = function(postfix) {
91
+ return this.constructor._$anchor(postfix, this.fields_id);
92
+ };
93
+
94
+ DynamicFields.prototype._render_insertion = function(template_type) {
95
+ var template;
96
+ template = this.$fields_begin.data("dynamicFields" + template_type + "Template");
97
+ return template.replace(/([_\[])dynamic_fields_index([_\]])/g, "$1" + (new Date().getTime() + this.constructor._counter++) + "$2");
98
+ };
99
+
100
+ return DynamicFields;
101
+
102
+ })();
103
+
104
+ $(document).on('click', '[data-dynamic-fields-remove]', function(event) {
105
+ event.preventDefault();
106
+ return DynamicFields.remove(this);
107
+ });
108
+
109
+ $(document).on('click', '[data-dynamic-fields-add]', function(event) {
110
+ event.preventDefault();
111
+ return DynamicFields.add(this);
112
+ });
113
+
114
+ }).call(this);
@@ -0,0 +1,9 @@
1
+ require 'dynamic-fields-for/version'
2
+ require 'jquery-rails'
3
+ require 'association-soft-build'
4
+
5
+ module DynamicFieldsFor
6
+ autoload :FormHelper, 'dynamic-fields-for/form_helper'
7
+ end
8
+
9
+ require 'dynamic-fields-for/railtie'
@@ -0,0 +1,69 @@
1
+ module DynamicFieldsFor
2
+ module FormHelper
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ alias_method_chain :fields_for, :dynamic_fields
7
+ alias_method_chain :fields_for_nested_model, :dynamic_fields
8
+ end
9
+
10
+ def fields_for_with_dynamic_fields(association, record_object = nil, options = {}, &block)
11
+ #Inherit the native parameters adjustment
12
+ options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
13
+
14
+ return fields_for_without_dynamic_fields(association, record_object, options, &block) unless options.delete(:dynamic)
15
+
16
+ new_object = @object.send(association).soft_build
17
+
18
+ options[:child_index] = 'dynamic_fields_index'
19
+ remove_template = fields_for_without_dynamic_fields(association, new_object, options) do |f|
20
+ f.hidden_field(:id, value: 'dynamic_fields_object_id') +
21
+ f.hidden_field(:_destroy, value: true)
22
+ end
23
+
24
+ options[:dynamic_fields_id] = dynamic_fields_id(association)
25
+ add_template = fields_for_without_dynamic_fields(association, new_object, options, &block)
26
+
27
+ options.delete(:child_index)
28
+ collection_output = fields_for_without_dynamic_fields(association, record_object, options, &block)
29
+
30
+ @template.content_tag(:script, nil, data: {
31
+ 'dynamic-fields-begin' => options[:dynamic_fields_id],
32
+ 'dynamic-fields-add-template' => CGI.escapeHTML(add_template).html_safe,
33
+ 'dynamic-fields-remove-template' => CGI.escapeHTML(remove_template).html_safe
34
+ }) +
35
+ collection_output +
36
+ @template.content_tag(:script, nil, data: {
37
+ 'dynamic-fields-end' => options[:dynamic_fields_id]
38
+ })
39
+ end
40
+
41
+ def add_fields_link(association, label, options = {})
42
+ @template.link_to(label, '#', {data: {'dynamic-fields-add' => dynamic_fields_id(association)}}.deep_merge(options))
43
+ end
44
+
45
+ def remove_fields_link(label, options = {})
46
+ data_options = {data: {'dynamic-fields-remove' => @options[:dynamic_fields_id]}}
47
+ data_options[:data]['dynamic-fields-remove-id'] = @object.id if @object.try(:persisted?)
48
+
49
+ @template.link_to(label, '#', data_options.deep_merge(options))
50
+ end
51
+
52
+ def fields_for_nested_model_with_dynamic_fields(name, object, fields_options, block)
53
+ dynamic_item_begin_mark = fields_options.has_key?(:dynamic_fields_id) ?
54
+ @template.content_tag(:script, nil, data: {
55
+ 'dynamic-fields-item-begin' => fields_options[:dynamic_fields_id]
56
+ }) : ''.html_safe
57
+
58
+ dynamic_item_begin_mark +
59
+ fields_for_nested_model_without_dynamic_fields(name, object, fields_options, block)
60
+ end
61
+
62
+ private
63
+
64
+ def dynamic_fields_id(association)
65
+ "#{self.object_id}-#{association}"
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,12 @@
1
+ require 'active_record/railtie'
2
+
3
+ module DynamicFieldsFor
4
+ class Engine < Rails::Engine
5
+
6
+ initializer 'dynamic-fields-for.configure_rails_initialization' do
7
+ ActiveSupport.on_load :action_view do
8
+ ActionView::Helpers::FormBuilder.send :include, DynamicFieldsFor::FormHelper
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module DynamicFieldsFor
2
+ VERSION = '1.0.0'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dynamic-fields-for
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sergey Tokarenko
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-05-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.13
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.2.13
30
+ - !ruby/object:Gem::Dependency
31
+ name: jquery-rails
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: association-soft-build
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Dynamic association fieldsets without pain.
79
+ email: private.tokarenko.sergey@gmail.com
80
+ executables: []
81
+ extensions: []
82
+ extra_rdoc_files: []
83
+ files:
84
+ - app/assets/javascripts/dynamic-fields-for.js
85
+ - lib/dynamic-fields-for/form_helper.rb
86
+ - lib/dynamic-fields-for/railtie.rb
87
+ - lib/dynamic-fields-for/version.rb
88
+ - lib/dynamic-fields-for.rb
89
+ - LICENSE
90
+ - README.md
91
+ homepage: https://github.com/stokarenko/dynamic-fields-for
92
+ licenses:
93
+ - MIT
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 1.8.23.2
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Dynamic association fieldsets without pain.
116
+ test_files: []