nested_editor_for 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ca3f776538ef3cac0f158b79dcb420d84a222383
4
+ data.tar.gz: 09f9a2c066e686e94a6352a2b370e10e7541077a
5
+ SHA512:
6
+ metadata.gz: 1c9ac93bf8cfe6b1be98e279871291910d61f0e2eaa8d957e237255b070efbfbd31acdbd8599fbf6a7c535c502cbd9499226ee43acdd9c5a557ec421505deec0
7
+ data.tar.gz: e1f6df80b5919a2d832e3ff82521dfbf94a224e609227f58edeb04a935a6459b1c16e61a7f2f90141b3b5a67f9d634cc41e95fd7b0310c65b5847c7766e5801e
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nested_editor_for.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # NestedEditorFor
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/nested_editor_for`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'nested_editor_for'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install nested_editor_for
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/nested_editor_for.
36
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "nested_editor_for"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,129 @@
1
+ module NestedEditorFor
2
+ module Builder
3
+ attr_reader :object, :template
4
+
5
+
6
+
7
+ # override: do arrays like attributes
8
+ def fields_for(method_or_object, *args, &block)
9
+ options = args.extract_options!
10
+ if @object
11
+ case method_or_object
12
+ when String, Symbol
13
+ object = @object.send method_or_object
14
+ if object.is_a?(Array) or object.is_a?(ActiveRecord::Relation)
15
+ name = options[:name] || "#{@object_name}[#{method_or_object}_attributes]"
16
+ return ((0...object.length).collect do |i|
17
+ template.fields_for("#{name}[#{i}]", object[i], *args, &block)
18
+ end).join.html_safe
19
+ else
20
+ name = options[:name] || method_or_object
21
+ return super(name, object, *args, &block)
22
+ end
23
+ end
24
+ end
25
+
26
+ super(method_or_object, *args, &block)
27
+ end
28
+
29
+
30
+
31
+ def hidden_field(method_or_object, *args)
32
+ method = case method_or_object
33
+ when String, Symbol; method_or_object
34
+ when Array; obj.first.class.name.tableize.singularize
35
+ else; obj.class.name.tableize.singularize
36
+ end
37
+
38
+ html_options = args.extract_options!
39
+ html_options[:id] = html_options[:name].parameterize.underscore if html_options[:name] && !html_options.key?(:id)
40
+ html_options["data-attr"] = method
41
+
42
+ super(method_or_object, html_options)
43
+ end
44
+
45
+
46
+
47
+ def static_field(method, value)
48
+ attr_name = "#{@object_name}[#{method}]"
49
+ html_options = {
50
+ 'data-attr' => method,
51
+ :type => "hidden",
52
+ :value => value,
53
+ :attr => attr_name,
54
+ :name => attr_name
55
+ }
56
+ template.tag("input", html_options)
57
+ end
58
+
59
+
60
+
61
+ def nested_editor_for(method, *args, &block)
62
+ raise ArgumentError, "Missing block" unless block_given?
63
+
64
+ template.instance_variable_set "@enable_nested_records", true
65
+
66
+ attr_name = "#{@object_name}[#{method}_attributes]"
67
+
68
+ # for some reason, things break if I make "#{@object_name}[#{object_name.to_s}_attributes]" the 'id' of the table
69
+ template.content_tag(:div, class: "nested editor") do
70
+ template.content_tag(:ol, attr: attr_name) do
71
+ nested_editor_wrapper(method, attr_name, &block)
72
+ end
73
+ end
74
+ end
75
+
76
+
77
+
78
+ private
79
+
80
+
81
+
82
+ def nested_editor_wrapper(method, attr_name, &block)
83
+ i = -1
84
+ fields_for(method, name: attr_name) do |f|
85
+ i += 1
86
+ nested_editor_row(f, attr_name, i, method, &block)
87
+ end
88
+ end
89
+
90
+ def nested_editor_row(f, attr_name, i, method, &block)
91
+ singular = method.to_s.singularize
92
+ attr_name = "#{attr_name}[#{i}]".html_safe # for sake of EditorBuilder which wants to pass "'+i+'"
93
+ html_options = {
94
+ class: "nested-row #{singular}",
95
+ :id => "#{singular}_#{i}".html_safe, # for sake of EditorBuilder which wants to pass "'+i+'"
96
+ :name => attr_name,
97
+ :attr => attr_name
98
+ }
99
+ template.content_tag(:li, html_options) do
100
+ nested_row_hidden_properties(f) <<
101
+ template.capture(f, &block) <<
102
+ delete_nested_command <<
103
+ add_nested_command
104
+ end
105
+ end
106
+
107
+ def nested_row_hidden_properties(f)
108
+ template.content_tag(:div, class: "hidden") do
109
+ (f.hidden_field :id) <<
110
+ (f.static_field :_destroy, (f.object.respond_to?(:_destroy) && f.object._destroy) ? 1 : 0)
111
+ end
112
+ end
113
+
114
+ def delete_nested_command
115
+ template.content_tag(:span, class: "delete-nested") do
116
+ "<a class=\"delete-link delete-nested-link\" href=\"#\" title=\"Delete\">Delete</a>".html_safe
117
+ end
118
+ end
119
+
120
+ def add_nested_command
121
+ template.content_tag(:span, class: "add-nested") do
122
+ "<a class=\"add-link add-nested-link\" href=\"#\" title=\"Add\">Add</a>".html_safe
123
+ end
124
+ end
125
+
126
+
127
+
128
+ end
129
+ end
@@ -0,0 +1,5 @@
1
+ # Configure Rails 3.1 to have assets in the load path
2
+ module NestedEditorFor
3
+ class Engine < Rails::Engine
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module NestedEditorFor
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ require "nested_editor_for/version"
2
+ require "nested_editor_for/engine"
3
+ require "nested_editor_for/builder"
4
+ require "action_view/helpers"
5
+
6
+ ActionView::Helpers::FormBuilder.send :prepend, NestedEditorFor::Builder
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'nested_editor_for/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "nested_editor_for"
8
+ spec.version = NestedEditorFor::VERSION
9
+ spec.authors = ["Bob Lail"]
10
+ spec.email = ["bob.lailfamily@gmail.com"]
11
+
12
+ spec.summary = %q{Extends FormBuilder to support editing child records}
13
+ spec.homepage = "https://github.com/boblail/nested_editor_for"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.10"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ end
@@ -0,0 +1,225 @@
1
+ (function () {
2
+
3
+ function enableNestedEditors() {
4
+ enableNestedEditorsIn(document.body);
5
+ }
6
+
7
+ function enableNestedEditorsIn(parent) {
8
+ withEach(findNestedEditors(parent), initializeNestedEditor);
9
+ }
10
+
11
+ function updateNestedEditors() {
12
+ updateNestedEditorsIn(document.body);
13
+ }
14
+
15
+ function updateNestedEditorsIn(parent) {
16
+ withEach(findNestedEditors(parent), updateNestedEditor);
17
+ }
18
+
19
+ function findNestedEditors(parent) {
20
+ return $(parent).find('.nested.editor');
21
+ }
22
+
23
+ function initializeNestedEditor(nested_editor) {
24
+ $(nested_editor).on('click', '.add-nested-link', nestedRowAction(addNestedRow));
25
+ $(nested_editor).on('click', '.delete-nested-link', nestedRowAction(deleteNestedRow));
26
+ updateNestedEditor(nested_editor);
27
+
28
+ // Hide rows that were created with _destroy="1"
29
+ var deleted_rows = $(nested_editor).find('[data-attr="_destroy"][value="1"]');
30
+ for(var i=0, ii=deleted_rows.length, row, input; i<ii; i++) {
31
+ input = deleted_rows[i];
32
+ row = $(input).closest('.nested-row')[0];
33
+ row && $(row).hide();
34
+ }
35
+ }
36
+
37
+ function nestedRowAction(action) {
38
+ return function(e) {
39
+ e.preventDefault();
40
+ e.stopPropagation();
41
+ var row = getNestedRowFromEvent(e);
42
+ row && action(row);
43
+ }
44
+ }
45
+
46
+ function getNestedRowFromEvent(e) {
47
+ var row = $(e.target).closest('.nested-row')[0];
48
+ row || FT.debug('[getParentNestedRow] .nested-row not found');
49
+ return row;
50
+ }
51
+
52
+
53
+
54
+ function addNestedRow(row) {
55
+ if(!row.parentNode) {return;}
56
+ var nested_editor = $(row).closest('.nested.editor')[0];
57
+
58
+ // Clone a row
59
+ var new_row = $(row).clone()[0],
60
+ name = $(new_row).attr('name').replace(/\[(\d+)\]/, function(m, n){return '['+(Number(n)+1)+']';});
61
+ new_row.id = row.id.replace(/(\d+)$/, function(m, n){return Number(n) + 1;});
62
+ $(new_row).attr('name', name);
63
+ $(new_row).attr('attr', name);
64
+ row.parentNode.appendChild(new_row);
65
+
66
+ // Reset the cloned row's values
67
+ setNestedRowFieldValue(new_row, '_destroy', 0);
68
+ setNestedRowFieldValue(new_row, 'id', '');
69
+ resetFormFieldsIn(new_row);
70
+ selectFirstFieldIn(new_row);
71
+
72
+ // observer.fire('after_add_nested', [nested_editor, new_row]);
73
+ updateNestedEditor(nested_editor);
74
+ }
75
+
76
+ function deleteNestedRow(row) {
77
+ if(!row.parentNode) {return;}
78
+ var nested_editor = $(row).closest('.nested.editor')[0];
79
+
80
+ // How many undeleted records are left? Only 1? Create a new empty record.
81
+ var undeleted_rows = 0;
82
+ withEach(row.parentNode.childNodes, function(row) {
83
+ (getNestedRowFieldValue(row, '_destroy') != '1') && (undeleted_rows++);
84
+ });
85
+ (undeleted_rows <= 1) && addNestedRow(row);
86
+
87
+ // Give focus either to the previous row or the next row
88
+ var previous_row = $(row).prev()[0] || $(row).next()[0];
89
+ selectFirstFieldIn(previous_row);
90
+
91
+ // Remove or hide the deleted record
92
+ if(getNestedRowFieldValue(row, 'id')) {
93
+ setNestedRowFieldValue(row, '_destroy', 1);
94
+ $(row).hide();
95
+ } else {
96
+ row.parentNode.removeChild(row);
97
+ }
98
+
99
+ // observer.fire('after_delete_nested', [nested_editor, row]);
100
+ updateNestedEditor(nested_editor);
101
+ }
102
+
103
+ function getNestedRowFieldValue(row, attr) {
104
+ var field = getNestedRowField(row, attr);
105
+ return field && field.value;
106
+ }
107
+
108
+ function setNestedRowFieldValue(row, attr, value) {
109
+ var field = getNestedRowField(row, attr);
110
+ field && (field.value = value);
111
+ }
112
+
113
+ function getNestedRowField(row, attr) {
114
+ return $(row).find('[data-attr="' + attr + '"]')[0];
115
+ }
116
+
117
+
118
+
119
+
120
+ function updateNestedEditor(nested_editor) {
121
+ var object_name = $(nested_editor).attr('name'),
122
+ rows = $(nested_editor).find('.nested-row'),
123
+ visible_rows = [],
124
+ row;
125
+
126
+ for(var i=0, ii=rows.length; i<ii; i++) {
127
+ row = rows[i];
128
+ renumberNestedRow(row, i);
129
+ (getNestedRowFieldValue(row, '_destroy') != '1') && visible_rows.push(row);
130
+ }
131
+
132
+ var ii = visible_rows.length - 1;
133
+ for(var i=0; i<ii; i++) { setAddNestedVisibility(visible_rows[i], 'hidden'); }
134
+ if(ii >= 0) { setAddNestedVisibility(visible_rows[ii], 'visible'); }
135
+
136
+ // observer.fire('after_reset_nested', nested_editor);
137
+ }
138
+
139
+ function renumberNestedRow(row, i) {
140
+ withEach($(row).find('input, textarea, select'), function(e) {
141
+ var name = $(e).attr('name');
142
+ if(name) {
143
+ $(e).attr('name', name.replace(/\[(\d+)\]/, function() { return '[' + i + ']'; }));
144
+ }
145
+ });
146
+ }
147
+
148
+ function setAddNestedVisibility(row, add_visibility) {
149
+ var add_link = $(row).find('.add-link')[0];
150
+ add_link && $(add_link).css({visibility: add_visibility});
151
+ }
152
+
153
+
154
+
155
+ function resetNestedEditor(nested_editor) {
156
+ var nested_rows = $(nested_editor).find('.nested-row');
157
+ for(var i=1, ii=nested_rows.length; i<ii; i++) {
158
+ var row = nested_rows[i];
159
+ row.parentNode.removeChild(row);
160
+ }
161
+ updateNestedEditor(nested_editor);
162
+ }
163
+
164
+
165
+
166
+ function withEach(array, fn) {
167
+ for(var i=0, ii=array.length; i<ii; i++) { fn(array[i]); }
168
+ }
169
+
170
+ function member(array, item) {
171
+ for(var i=0, ii=array.length; i<ii; i++) {
172
+ if(array[i] == item) { return true; }
173
+ }
174
+ return false;
175
+ }
176
+
177
+ function resetFormFieldsIn(parent, options) {
178
+ var inputs = $(parent).find('input[type="text"], input[type="tel"], input[type="email"], textarea'),
179
+ selects = $(parent).find('select'),
180
+ nested_editors = $(parent).find('.nested.editor');
181
+ options = options || {};
182
+
183
+ function fieldToBeReset(id) {
184
+ return !(options.only && !member(options.only, input.id)) &&
185
+ !(options.except && member(options.except, input.id));
186
+ }
187
+
188
+ for(var i=0, ii=inputs.length; i<ii; i++) {
189
+ var input = inputs[i];
190
+ fieldToBeReset(input.id) && (input.value = '');
191
+ }
192
+ for(var i=0, ii=selects.length; i<ii; i++) {
193
+ var select = selects[i];
194
+ fieldToBeReset(select.id) && (select.selectedIndex = 0);
195
+ }
196
+ for(var i=0, ii=nested_editors.length; i<ii; i++) {
197
+ resetNestedEditor(nested_editors[i]);
198
+ }
199
+ }
200
+
201
+ function selectFirstFieldIn(parent) {
202
+ var inputs = $(parent).find('input, select, textarea');
203
+ for(var i=0, ii=inputs.length; i<ii; i++) {
204
+ var input = inputs[i];
205
+ if($(input).is(':visible') && (input.type != 'hidden')) {
206
+ $(input).focus();
207
+ return;
208
+ }
209
+ }
210
+ }
211
+
212
+
213
+
214
+ window.NestedEditorFor = {
215
+ init: enableNestedEditors,
216
+ enableAll: enableNestedEditors,
217
+ enableIn: enableNestedEditorsIn,
218
+ updateAll: updateNestedEditors,
219
+ updateIn: updateNestedEditorsIn,
220
+
221
+ addRow: addNestedRow,
222
+ deleteRow: deleteNestedRow,
223
+ getRowFromEvent: getNestedRowFromEvent
224
+ };
225
+ })();
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nested_editor_for
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bob Lail
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-09-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description:
42
+ email:
43
+ - bob.lailfamily@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".travis.yml"
50
+ - Gemfile
51
+ - README.md
52
+ - Rakefile
53
+ - bin/console
54
+ - bin/setup
55
+ - lib/nested_editor_for.rb
56
+ - lib/nested_editor_for/builder.rb
57
+ - lib/nested_editor_for/engine.rb
58
+ - lib/nested_editor_for/version.rb
59
+ - nested_editor_for.gemspec
60
+ - vendor/assets/javascripts/nested_editor_for.js
61
+ homepage: https://github.com/boblail/nested_editor_for
62
+ licenses: []
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.2.2
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Extends FormBuilder to support editing child records
84
+ test_files: []