activeadmin_polymorphic 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/Gemfile +40 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +20 -0
- data/README.md +86 -0
- data/Rakefile +33 -0
- data/activeadmin_polymorphic.gemspec +19 -0
- data/app/assets/javascripts/activeadmin_polymorphic.js.coffee +172 -0
- data/app/assets/stylesheets/activeadmin_polymorphic.css.sass +25 -0
- data/lib/activeadmin_polymorphic/engine.rb +5 -0
- data/lib/activeadmin_polymorphic/form_builder.rb +124 -0
- data/lib/activeadmin_polymorphic/version.rb +3 -0
- data/lib/activeadmin_polymorphic.rb +5 -0
- data/spec/rails_helper.rb +154 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/deferred_garbage_collection.rb +19 -0
- data/spec/support/detect_rails_version.rb +26 -0
- data/spec/support/integration_example_group.rb +31 -0
- data/spec/support/jslint.yml +80 -0
- data/spec/support/rails_template.rb +104 -0
- data/spec/support/rails_template_with_data.rb +59 -0
- data/spec/support/templates/cucumber.rb +24 -0
- data/spec/support/templates/cucumber_with_reloading.rb +5 -0
- data/spec/support/templates/en.yml +8 -0
- data/spec/support/templates/policies/active_admin/comment_policy.rb +9 -0
- data/spec/support/templates/policies/active_admin/page_policy.rb +18 -0
- data/spec/support/templates/policies/application_policy.rb +45 -0
- data/spec/support/templates/policies/category_policy.rb +7 -0
- data/spec/support/templates/policies/post_policy.rb +15 -0
- data/spec/support/templates/policies/store_policy.rb +11 -0
- data/spec/support/templates/policies/user_policy.rb +11 -0
- data/spec/support/templates/post_decorator.rb +11 -0
- data/spec/unit/form_builder_spec.rb +121 -0
- data/tasks/test.rake +83 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 806875f4dc043b24f94549838a81c5ad6d180e4b
|
4
|
+
data.tar.gz: 786835f0298f3ce1e08870a9bede6f9ab036d9e6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8a5da9426cf627abefc48134e70fc9bea72ebd90de1f8c1e28094e6dc693037609120a1ed514a26a1d5b417ed64a6d558623f8a47ffcab26b0585eee6b60b2e5
|
7
|
+
data.tar.gz: d8f296a37a267d68ccccf8d9cfea1b8449f7ebe66b3a889e1791ad29126093191a68bde27f27c8834cb093d4aa64a03bd8eff151f36b3c0e2a65bede0e26e666
|
data/.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
*.gem
|
2
|
+
## Mac
|
3
|
+
.DS_Store
|
4
|
+
|
5
|
+
## Windows
|
6
|
+
.Thumbs.db
|
7
|
+
|
8
|
+
## TextMate
|
9
|
+
*.tm_project
|
10
|
+
*.tmproj
|
11
|
+
tmtags
|
12
|
+
|
13
|
+
## Emacs
|
14
|
+
*~
|
15
|
+
\#*
|
16
|
+
.\#*
|
17
|
+
|
18
|
+
## Vim
|
19
|
+
*.swp
|
20
|
+
# IDEA / RUBYMINE
|
21
|
+
.idea
|
22
|
+
|
23
|
+
## RVM
|
24
|
+
.rvmrc
|
25
|
+
.ruby-version
|
26
|
+
.ruby-gemset
|
27
|
+
|
28
|
+
## Project (general)
|
29
|
+
tags
|
30
|
+
coverage
|
31
|
+
rdoc
|
32
|
+
doc
|
33
|
+
.yardoc
|
34
|
+
pkg
|
35
|
+
|
36
|
+
## Project (specific)
|
37
|
+
bin/
|
38
|
+
.bundle
|
39
|
+
spec/rails
|
40
|
+
*.sqlite3-journal
|
41
|
+
Gemfile.lock
|
42
|
+
Gemfile-*.lock
|
43
|
+
capybara*
|
44
|
+
viewcumber
|
45
|
+
test-rails*
|
46
|
+
public
|
47
|
+
.rspec
|
48
|
+
.rails-version
|
49
|
+
.rbenv-version
|
50
|
+
.localeapp/*
|
data/Gemfile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
gem 'activeadmin', github: 'activeadmin'
|
6
|
+
gem 'inherited_resources'
|
7
|
+
|
8
|
+
group :development do
|
9
|
+
# Debugging
|
10
|
+
gem 'better_errors' # Web UI to debug exceptions. Go to /__better_errors to access the latest one
|
11
|
+
gem 'binding_of_caller' # Retrieve the binding of a method's caller in MRI Ruby >= 1.9.2
|
12
|
+
|
13
|
+
# Performance
|
14
|
+
gem 'rack-mini-profiler' # Inline app profiler. See ?pp=help for options.
|
15
|
+
gem 'flamegraph' # Flamegraph visualiztion: ?pp=flamegraph
|
16
|
+
|
17
|
+
# Documentation
|
18
|
+
gem 'yard' # Documentation generator
|
19
|
+
gem 'redcarpet' # Markdown implementation (for yard)
|
20
|
+
end
|
21
|
+
|
22
|
+
group :test do
|
23
|
+
gem 'capybara'
|
24
|
+
gem 'simplecov', require: false # Test coverage generator. Go to /coverage/ after running tests
|
25
|
+
gem 'coveralls', require: false # Test coverage website. Go to https://coveralls.io
|
26
|
+
gem 'cucumber-rails', require: false
|
27
|
+
gem 'database_cleaner'
|
28
|
+
gem 'guard-rspec'
|
29
|
+
gem 'jasmine'
|
30
|
+
gem 'jslint_on_rails'
|
31
|
+
gem 'launchy'
|
32
|
+
gem 'rails-i18n' # Provides default i18n for many languages
|
33
|
+
gem 'rspec'
|
34
|
+
gem 'rspec-mocks'
|
35
|
+
gem 'rspec-rails'
|
36
|
+
gem 'i18n-spec'
|
37
|
+
gem 'shoulda-matchers'
|
38
|
+
gem 'sqlite3'
|
39
|
+
gem 'poltergeist'
|
40
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# More info at https://github.com/guard/guard#readme
|
2
|
+
|
3
|
+
guard 'rspec', all_on_start: false, cmd: "bundle exec rspec" do
|
4
|
+
watch(%r{^spec/.+_spec\.rb$})
|
5
|
+
watch(%r{^lib/active_admin_polymorphic/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
|
6
|
+
watch('spec/spec_helper.rb') { "spec/" }
|
7
|
+
watch('spec/rails_helper.rb') { "spec/" }
|
8
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 Sergeev Peter
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# ActiveadminPolymorphic
|
2
|
+
|
3
|
+
Activeadmin Polymorphic gem is made to bring has_many polymorphic nested forms into your ActiveAdmin. ActiveAdmin users formtastic to build awesome forms, but formatstic itself doesn't support polymorphic relations. `activeadmin_polymorphic` gem is trying to solve that problem.
|
4
|
+
|
5
|
+
![](https://s3.amazonaws.com/f.cl.ly/items/0b2F2t2R3D0o1O3F1R3e/Screen%20Shot%202015-02-05%20at%2012.57.04.png)
|
6
|
+
|
7
|
+
# Features
|
8
|
+
|
9
|
+
* polymorphic forms
|
10
|
+
* validation
|
11
|
+
* sortable behaviour
|
12
|
+
* file uploads
|
13
|
+
|
14
|
+
# Installation
|
15
|
+
|
16
|
+
Add this to your Gemfile:
|
17
|
+
|
18
|
+
``` ruby
|
19
|
+
gem "activeadmin_polymorphic"
|
20
|
+
```
|
21
|
+
|
22
|
+
and run `bundle install`.
|
23
|
+
|
24
|
+
Include assets in js and css manifests
|
25
|
+
|
26
|
+
```
|
27
|
+
#= require activeadmin_polymorphic
|
28
|
+
@import "activeadmin_polymorphic";
|
29
|
+
```
|
30
|
+
|
31
|
+
# Usage
|
32
|
+
|
33
|
+
To use gem, your model should have related model, which works as a proxy to polymorphic relations.
|
34
|
+
|
35
|
+
![](https://s3.amazonaws.com/f.cl.ly/items/2Z3M2V0b3Z342L2Z2R0N/Screen%20Shot%202015-02-05%20at%2013.37.36.png)
|
36
|
+
|
37
|
+
Gem extrands activeadmin's form builder, so to enable `has_many_polymorphic` method you need to override form builder using `builder` option:
|
38
|
+
|
39
|
+
```
|
40
|
+
...
|
41
|
+
SECTIONABLES = [Image, Text]
|
42
|
+
|
43
|
+
form builder: ActiveadminPolymorphic::FormBuilder do |f|
|
44
|
+
f.polymorphic_has_many :sections, :sectionable, types: SECTIONABLES
|
45
|
+
end
|
46
|
+
...
|
47
|
+
```
|
48
|
+
|
49
|
+
There are few options available:
|
50
|
+
* first option is a name of polymorphic has_many association
|
51
|
+
* second option referes to polymorphied version of association name (sectionable_id and sectaionable_type for example)
|
52
|
+
* `types` - list of related models you want to use
|
53
|
+
* `allow_destroy` - weather or not to allow to destroy related objects
|
54
|
+
* `sortable` - enables drag'n'drop for nested forms, accepts sortable column name, for example `sortable: :priority`
|
55
|
+
|
56
|
+
Subforms for polymorphic relations are forms which you define in your ActiveAdmin. Gem fetches and submits them using some ajax magic.
|
57
|
+
|
58
|
+
# Under the hood
|
59
|
+
|
60
|
+
This gem is a set of dirty hacks and tricks. Calling `polymorphic_has_many` makes it to do the following things:
|
61
|
+
|
62
|
+
* for new records it generates dropdown with polymorphic types
|
63
|
+
* for exising records it generates two hidden fields with id and type
|
64
|
+
* then the real javascript starts, it extracts whole forms from polymorphic models new or edit pages, strips form actions, and inserts that forms right into parent form
|
65
|
+
* when you try to submit forms, javascripts submit subforms first; if subforms are invalid, it reloads them with erros and interupts main form submission process
|
66
|
+
* after all sub forms successfully saved, it strips them (because forms nested into other forms are simantically invalid, right?) and submits parent form
|
67
|
+
|
68
|
+
# File uploads in subforms
|
69
|
+
|
70
|
+
Gem relies on [rails ajax](https://github.com/rails/jquery-rails) form submissions, which doesn't allow to submit files directly. Workaround for it is asynchronous file submission using for example [remotipart](https://github.com/JangoSteve/remotipart) for CarrierWave or [refile](https://github.com/elabs/refile) with [refile-input](https://github.com/hyperoslo/refile-input). Note: before subforms submissions javascript strips all file inputs from forms.
|
71
|
+
|
72
|
+
# Testing
|
73
|
+
|
74
|
+
Tests stucture is mostly copied from original ActiveAdmin.
|
75
|
+
|
76
|
+
Install development dependencies with `bundle install`. To setup test suit run `rake test`. Run tests with `bundle exec guard`. There aren't many of them, let's say there are quite a few.
|
77
|
+
|
78
|
+
# In plan
|
79
|
+
|
80
|
+
* allow to reuse existing polymorphic objects
|
81
|
+
* check who it works with models under sertain namespace
|
82
|
+
* improve tests
|
83
|
+
|
84
|
+
# License
|
85
|
+
|
86
|
+
[MIT](LICENSE.txt)
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rake'
|
3
|
+
Bundler.setup
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
def cmd(command)
|
7
|
+
puts command
|
8
|
+
fail unless system command
|
9
|
+
end
|
10
|
+
|
11
|
+
require File.expand_path('../spec/support/detect_rails_version', __FILE__)
|
12
|
+
|
13
|
+
# Import all our rake tasks
|
14
|
+
FileList['tasks/**/*.rake'].each { |task| import task }
|
15
|
+
|
16
|
+
task default: :test
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'jasmine'
|
20
|
+
load 'jasmine/tasks/jasmine.rake'
|
21
|
+
rescue LoadError
|
22
|
+
task :jasmine do
|
23
|
+
abort 'Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
task :console do
|
28
|
+
require 'irb'
|
29
|
+
require 'irb/completion'
|
30
|
+
|
31
|
+
ARGV.clear
|
32
|
+
IRB.start
|
33
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require "activeadmin_polymorphic/version"
|
5
|
+
|
6
|
+
# Describe your gem and declare its dependencies:
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.license = "MIT"
|
9
|
+
s.name = "activeadmin_polymorphic"
|
10
|
+
s.version = ActiveadminPolymorphic::VERSION
|
11
|
+
s.authors = ["Petr Sergeev", "Sindre Moen"]
|
12
|
+
s.email = ["peter@hyper.no", "sindre@hyper.no"]
|
13
|
+
s.description = 'This gem extends formtastic\'s form builder to support polymoprhic has many relations in your forms'
|
14
|
+
s.summary = 'HasMany polymoprhic support for active admin.'
|
15
|
+
s.homepage = "https://github.com/hyperoslo/activeadmin-polymorphic"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n").sort
|
18
|
+
s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
|
19
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
$ ->
|
2
|
+
if $('.polymorphic_has_many_container').length
|
3
|
+
form = $('#main_content').find('form:first')
|
4
|
+
$(form).on 'submit', (e) ->
|
5
|
+
submissions_counter = 0
|
6
|
+
parentForm = @
|
7
|
+
expect = $(@).find('form').length
|
8
|
+
if submissions_counter < expect
|
9
|
+
e.preventDefault()
|
10
|
+
|
11
|
+
$(@).find('form').each ->
|
12
|
+
remoteSubmit @, ->
|
13
|
+
submissions_counter++
|
14
|
+
if submissions_counter == expect
|
15
|
+
$(form).find('form').remove()
|
16
|
+
stripEmptyRelations()
|
17
|
+
$(parentForm).submit()
|
18
|
+
|
19
|
+
$(document).on "upload:start", "form", (event) ->
|
20
|
+
form = $('#main_content').find('form:first')
|
21
|
+
form.find("input[type=submit]").attr "disabled", true
|
22
|
+
|
23
|
+
$(document).on "upload:complete", "form", (event) ->
|
24
|
+
form = $('#main_content').find('form:first')
|
25
|
+
|
26
|
+
unless form.find("input.uploading").length
|
27
|
+
form.find("input[type=submit]").removeAttr "disabled"
|
28
|
+
|
29
|
+
$('.polymorphic_has_many_fields').each (index, rapper) ->
|
30
|
+
rapper = $ rapper
|
31
|
+
|
32
|
+
hiddenField = rapper.find 'input[type=hidden][data-path]'
|
33
|
+
formPath = hiddenField.data 'path'
|
34
|
+
|
35
|
+
extractAndInsertForm formPath, rapper
|
36
|
+
|
37
|
+
|
38
|
+
$(document).on 'click', 'a.button.polymorphic_has_many_remove', (e)->
|
39
|
+
e.preventDefault()
|
40
|
+
parent = $(@).closest '.polymorphic_has_many_container'
|
41
|
+
to_remove = $(@).closest 'fieldset'
|
42
|
+
recompute_positions parent
|
43
|
+
|
44
|
+
parent.trigger 'polymorphic_has_many_remove:before', [to_remove, parent]
|
45
|
+
to_remove.remove()
|
46
|
+
parent.trigger 'polymorphic_has_many_remove:after', [to_remove, parent]
|
47
|
+
|
48
|
+
$(document).on 'click', 'a.button.polymorphic_has_many_add', (e)->
|
49
|
+
e.preventDefault()
|
50
|
+
parent = $(@).closest '.polymorphic_has_many_container'
|
51
|
+
parent.trigger before_add = $.Event('polymorphic_has_many_add:before'), [parent]
|
52
|
+
|
53
|
+
unless before_add.isDefaultPrevented()
|
54
|
+
index = parent.data('polymorphic_has_many_index') || parent.children('fieldset').length - 1
|
55
|
+
parent.data has_many_index: ++index
|
56
|
+
|
57
|
+
regex = new RegExp $(@).data('placeholder'), 'g'
|
58
|
+
html = $(@).data('html').replace regex, index
|
59
|
+
|
60
|
+
fieldset = $(html).insertBefore(@)
|
61
|
+
recompute_positions parent
|
62
|
+
parent.trigger 'polymorphic_has_many_add:after', [fieldset, parent]
|
63
|
+
|
64
|
+
init_polymorphic_sortable()
|
65
|
+
|
66
|
+
|
67
|
+
$('.polymorphic_has_many_container').on 'change', '.polymorphic_type_select', (event) ->
|
68
|
+
fieldset = $(this).closest 'fieldset'
|
69
|
+
|
70
|
+
selectedOption = $(this).find 'option:selected'
|
71
|
+
formPath = selectedOption.data 'path'
|
72
|
+
|
73
|
+
label = $(this).prev 'label'
|
74
|
+
label.remove()
|
75
|
+
|
76
|
+
hiddenField = $('<input type="hidden" />')
|
77
|
+
hiddenField.attr 'name', $(this).attr('name')
|
78
|
+
hiddenField.val $(this).val()
|
79
|
+
|
80
|
+
$(this).replaceWith hiddenField
|
81
|
+
|
82
|
+
newListItem = $ '<li>'
|
83
|
+
|
84
|
+
extractAndInsertForm formPath, fieldset
|
85
|
+
|
86
|
+
init_polymorphic_sortable = ->
|
87
|
+
elems = $('.polymorphic_has_many_container[data-sortable]:not(.ui-sortable)')
|
88
|
+
|
89
|
+
elems.sortable
|
90
|
+
axis: 'y'
|
91
|
+
items: '> fieldset',
|
92
|
+
handle: '> ol > .handle',
|
93
|
+
stop: recompute_positions
|
94
|
+
elems.each recompute_positions
|
95
|
+
|
96
|
+
# Removes relations if id or type is not specified
|
97
|
+
# For example when user clicked add relation button, but didn't selected type
|
98
|
+
stripEmptyRelations = ->
|
99
|
+
$('.polymorphic_has_many_fields input:hidden').each ->
|
100
|
+
if $(@).val() == ""
|
101
|
+
$(@).parents('.polymorphic_has_many_fields').remove()
|
102
|
+
|
103
|
+
recompute_positions = (parent)->
|
104
|
+
parent = if parent instanceof jQuery then parent else $(@)
|
105
|
+
input_name = parent.data 'sortable'
|
106
|
+
position = parseInt(parent.data('sortable-start') || 0, 10)
|
107
|
+
|
108
|
+
parent.children('fieldset').each ->
|
109
|
+
# We ignore nested inputs, so when defining your has_many, be sure to keep
|
110
|
+
# your sortable input at the root of the has_many block.
|
111
|
+
destroy_input = $(@).find "> ol > .input > :input[name$='[_destroy]']"
|
112
|
+
sortable_input = $(@).find "> ol > .input > :input[name$='[#{input_name}]']"
|
113
|
+
|
114
|
+
if sortable_input.length
|
115
|
+
sortable_input.val if destroy_input.is ':checked' then '' else position++
|
116
|
+
|
117
|
+
window.extractAndInsertForm= (url, target)->
|
118
|
+
target = $ target
|
119
|
+
|
120
|
+
$.get url, (data)->
|
121
|
+
elements = $(data)
|
122
|
+
form = $('#main_content form', elements).first()
|
123
|
+
$(form).find('.actions').remove()
|
124
|
+
$(form).on 'submit', -> return false
|
125
|
+
|
126
|
+
target.prepend form
|
127
|
+
|
128
|
+
window.loadErrors = (target) ->
|
129
|
+
$(target).off('ajax:success') # unbind successfull action for json form
|
130
|
+
$(target).trigger('submit.rails').on 'ajax:success', (event, data, result) ->
|
131
|
+
# duplicates method above. refactor using callbacks
|
132
|
+
elements = $(data)
|
133
|
+
form = $('#main_content form', elements).first()
|
134
|
+
$(form).find('.actions').remove()
|
135
|
+
$(form).on 'submit', -> return false
|
136
|
+
|
137
|
+
$(target).replaceWith(form)
|
138
|
+
|
139
|
+
|
140
|
+
window.remoteSubmit = (target, callback)->
|
141
|
+
$(target).data('remote', true)
|
142
|
+
$(target).removeAttr('novalidate')
|
143
|
+
action = $(target).attr('action')
|
144
|
+
$(target).find("input[type=file]").remove()
|
145
|
+
# we gonna burn in hell for that
|
146
|
+
# perhaps we can use ajax:before callback
|
147
|
+
# to set type json
|
148
|
+
action_with_json = action + '.json'
|
149
|
+
$(target).attr('action', action_with_json)
|
150
|
+
|
151
|
+
# unbind callbacks action for form if it was submitted before
|
152
|
+
$(target).off('ajax:success').off('ajax:aborted:file').off('ajax:error')
|
153
|
+
|
154
|
+
$(target).trigger('submit.rails')
|
155
|
+
.on 'ajax:aborted:file', (inputs) ->
|
156
|
+
false
|
157
|
+
.on 'ajax:error', (event, response, status)->
|
158
|
+
$(target).attr('action', action)
|
159
|
+
if response.status == 422
|
160
|
+
loadErrors(target)
|
161
|
+
.on 'ajax:success', (event, object, status, response) ->
|
162
|
+
$(target).attr('action', action)
|
163
|
+
if response.status == 201 # created
|
164
|
+
$(target).next().find('input:first').val(object.id)
|
165
|
+
# replace new form with edit form
|
166
|
+
# to update form method to PATCH and form action
|
167
|
+
url = "#{action}/#{object.id}/edit"
|
168
|
+
extractAndInsertForm(url, $(target).parent('fieldset'))
|
169
|
+
$(target).remove()
|
170
|
+
|
171
|
+
callback()
|
172
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
form .polymorphic_has_many_container fieldset
|
2
|
+
position: relative
|
3
|
+
|
4
|
+
form .polymorphic_has_many_container fieldset:not(:last-of-type)
|
5
|
+
border-bottom: 1px solid lightgray
|
6
|
+
margin-bottom: 20px
|
7
|
+
|
8
|
+
.input.refile img.image-preview
|
9
|
+
margin: 0.5em 0 0 20%
|
10
|
+
display: block
|
11
|
+
|
12
|
+
form .polymorphic_has_many_container .handle
|
13
|
+
position: absolute
|
14
|
+
top: calc(50% - 60px)
|
15
|
+
right: 2px
|
16
|
+
padding: 0
|
17
|
+
cursor: move
|
18
|
+
|
19
|
+
&.ui-sortable-handle
|
20
|
+
top: calc(50% - 42px)
|
21
|
+
|
22
|
+
span.icon
|
23
|
+
width: 3em
|
24
|
+
height: 3em
|
25
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module ActiveadminPolymorphic
|
2
|
+
class FormBuilder < ::ActiveAdmin::FormBuilder
|
3
|
+
def polymorphic_has_many(assoc, poly_name, options = {}, &block)
|
4
|
+
custom_settings = :new_record, :allow_destroy, :heading, :sortable, :sortable_start, :types, :path_prefix
|
5
|
+
builder_options = {new_record: true, path_prefix: :admin}.merge! options.slice *custom_settings
|
6
|
+
|
7
|
+
options = {for: assoc }.merge! options.except *custom_settings
|
8
|
+
options[:class] = [options[:class], "polymorphic_has_many_fields"].compact.join(' ')
|
9
|
+
sortable_column = builder_options[:sortable]
|
10
|
+
sortable_start = builder_options.fetch(:sortable_start, 0)
|
11
|
+
|
12
|
+
html = "".html_safe
|
13
|
+
html << template.capture do
|
14
|
+
contents = "".html_safe
|
15
|
+
|
16
|
+
block = polymorphic_form(poly_name, builder_options)
|
17
|
+
|
18
|
+
template.assign('polymorphic_has_many_block' => true)
|
19
|
+
contents = without_wrapper { inputs(options, &block) }
|
20
|
+
|
21
|
+
if builder_options[:new_record]
|
22
|
+
contents << js_for_polymorphic_has_many(
|
23
|
+
assoc, poly_name, template, builder_options, options[:class]
|
24
|
+
)
|
25
|
+
else
|
26
|
+
contents
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
tag = @already_in_an_inputs_block ? :li : :div
|
31
|
+
html = template.content_tag(tag, html, class: "polymorphic_has_many_container #{assoc}", 'data-sortable' => sortable_column, 'data-sortable-start' => sortable_start)
|
32
|
+
template.concat(html) if template.output_buffer
|
33
|
+
html
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def polymorphic_has_many_actions(has_many_form, builder_options, contents)
|
39
|
+
if has_many_form.object.new_record?
|
40
|
+
contents << template.content_tag(:li) do
|
41
|
+
template.link_to I18n.t('active_admin.has_many_remove'),
|
42
|
+
"#", class: 'button polymorphic_has_many_remove'
|
43
|
+
end
|
44
|
+
elsif builder_options[:allow_destroy]
|
45
|
+
contents << has_many_form.input(:_destroy, as: :boolean,
|
46
|
+
wrapper_html: {class: 'polymorphic_has_many_delete'},
|
47
|
+
label: I18n.t('active_admin.has_many_delete'))
|
48
|
+
end
|
49
|
+
|
50
|
+
if builder_options[:sortable]
|
51
|
+
contents << has_many_form.input(builder_options[:sortable], as: :hidden)
|
52
|
+
|
53
|
+
contents << template.content_tag(:li, class: 'handle') do
|
54
|
+
::ActiveAdmin::Iconic.icon :move_vertical
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
contents
|
59
|
+
end
|
60
|
+
|
61
|
+
def js_for_polymorphic_has_many(assoc, poly_name, template, builder_options, class_string)
|
62
|
+
new_record = builder_options[:new_record]
|
63
|
+
assoc_reflection = object.class.reflect_on_association assoc
|
64
|
+
assoc_name = assoc_reflection.klass.model_name
|
65
|
+
placeholder = "NEW_#{assoc_name.to_s.underscore.upcase.gsub(/\//, '_')}_RECORD"
|
66
|
+
|
67
|
+
text = new_record.is_a?(String) ? new_record : I18n.t('active_admin.has_many_new', model: assoc_name.human)
|
68
|
+
form_block = polymorphic_form(poly_name, builder_options, true)
|
69
|
+
|
70
|
+
opts = {
|
71
|
+
for: [assoc, assoc_reflection.klass.new],
|
72
|
+
class: class_string,
|
73
|
+
for_options: { child_index: placeholder }
|
74
|
+
}
|
75
|
+
|
76
|
+
html = "".html_safe
|
77
|
+
html << template.capture do
|
78
|
+
inputs_for_nested_attributes opts, &form_block
|
79
|
+
end
|
80
|
+
|
81
|
+
template.link_to text, '#', class: "button polymorphic_has_many_add", data: {
|
82
|
+
html: CGI.escapeHTML(html).html_safe, placeholder: placeholder
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def polymorphic_options(builder_options)
|
87
|
+
# add internationalization
|
88
|
+
builder_options[:types].each_with_object([]) do |model, options|
|
89
|
+
options << [
|
90
|
+
model.model_name.human, model,
|
91
|
+
{"data-path" => form_new_path(model, builder_options) }
|
92
|
+
]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def polymorphic_form(poly_name, builder_options, for_js = false)
|
97
|
+
proc do |f|
|
98
|
+
html = "".html_safe
|
99
|
+
html << f.input("#{poly_name}_id", as: :hidden)
|
100
|
+
|
101
|
+
if f.object.send(poly_name).nil?
|
102
|
+
html << f.input("#{poly_name}_type", input_html: { class: 'polymorphic_type_select' }, as: :select, collection: polymorphic_options(builder_options))
|
103
|
+
else
|
104
|
+
html << f.input(
|
105
|
+
"#{poly_name}_type", as: :hidden,
|
106
|
+
input_html: {"data-path" => form_edit_path(f.object.send(poly_name), builder_options) }
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
html << polymorphic_has_many_actions(f, builder_options, "".html_safe)
|
111
|
+
|
112
|
+
html
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def form_new_path(object, builder_options)
|
117
|
+
"/#{builder_options[:path_prefix]}/#{ActiveModel::Naming.plural(object)}/new"
|
118
|
+
end
|
119
|
+
|
120
|
+
def form_edit_path(object, builder_options)
|
121
|
+
"/#{builder_options[:path_prefix]}/#{ActiveModel::Naming.plural(object)}/#{object.id}/edit"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|