activeadmin_select_many 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +39 -16
- data/app/assets/javascripts/activeadmin/select_many.js +30 -30
- data/app/assets/stylesheets/activeadmin/_select_many.sass +23 -5
- data/config/locales/en.yml +2 -0
- data/lib/activeadmin/select_many/version.rb +1 -1
- data/lib/activeadmin_select_many.rb +1 -1
- data/lib/formtastic/inputs/select_many_input.rb +12 -15
- data/lib/formtastic/inputs/select_one_input.rb +14 -49
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc928a64768e531eb353bfb5aecc59b92151a4ff
|
4
|
+
data.tar.gz: 8b4ff87b759f176fa8f1d0a0806e5bff7be878be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b754c3cf2c094ea2eec2d805b8cf99b760680d74471fadcaa35ea8fe4437d0e05f75bded2ac68e817722b9005e093db3e84f44102d0abd8eacfbf8846c7f1fc
|
7
|
+
data.tar.gz: 6244aa2b14352c08cc696319dc93f49d79c44a49f59d8176d572cfc8c71f5aa12e9786a912bc180e98012367be57ee5b6cfb08bf6dd00ca0203e518bead4991c
|
data/README.md
CHANGED
@@ -1,17 +1,25 @@
|
|
1
1
|
# ActiveAdmin Select Many [![Gem Version](https://badge.fury.io/rb/activeadmin_select_many.svg)](https://badge.fury.io/rb/activeadmin_select_many)
|
2
2
|
|
3
|
-
An Active Admin plugin which improves one-to-many
|
3
|
+
An Active Admin plugin which improves one-to-many / many-to-many / many-to-one associations selection using 2 new inputs: **select_many** and **select_one** (jQuery required)
|
4
4
|
|
5
|
-
Features
|
5
|
+
Features for *select_many*:
|
6
6
|
- search box
|
7
|
-
-
|
8
|
-
- select on the right with selected items
|
7
|
+
- available items on the left, selected items on the right
|
9
8
|
- local/remote collections
|
10
9
|
- double click to add/remove items
|
11
|
-
- sortable
|
10
|
+
- sortable (with up/down buttons)
|
11
|
+
|
12
|
+
Features for *select_one*:
|
13
|
+
- search box
|
14
|
+
- selected items on the right
|
15
|
+
- remote collections
|
16
|
+
- counter of items found
|
17
|
+
- can be used as filter
|
12
18
|
|
13
19
|
![screenshot](screenshot.png)
|
14
20
|
|
21
|
+
*(inspired by RailsAdmin associations selector)*
|
22
|
+
|
15
23
|
## Install
|
16
24
|
|
17
25
|
- Add to your Gemfile:
|
@@ -23,7 +31,18 @@ Features:
|
|
23
31
|
`//= require activeadmin/select_many`
|
24
32
|
- Use the input with `as: :select_many` in Active Admin model conf
|
25
33
|
|
26
|
-
##
|
34
|
+
## Options
|
35
|
+
|
36
|
+
- **collection**: local collection
|
37
|
+
- **filter_form**: for *select_one* only, allow to use it as filter
|
38
|
+
- **placeholder**: placeholder string for search box
|
39
|
+
- **remote_collection**: JSON path
|
40
|
+
- **search_param**: parameter to use as search key (ransack format)
|
41
|
+
- **sortable**: set to true to enable sortable buttons (default: not set)
|
42
|
+
- **size**: number of rows of both the selects (default: 4)
|
43
|
+
- **text_key**: key to use as text for select options
|
44
|
+
|
45
|
+
## Example with select_many
|
27
46
|
|
28
47
|
Add to ActiveAdmin model config, in *form* block.
|
29
48
|
|
@@ -35,8 +54,10 @@ Add to ActiveAdmin model config, in *form* block.
|
|
35
54
|
`f.input :tags, as: :select_many, remote_collection: admin_tags_path( format: :json ), search_param: 'category_contains', text_key: 'category', placeholder: 'Type something...'`
|
36
55
|
- Sortable (items position must be saved manually):
|
37
56
|
`f.input :tags, as: :select_many, remote_collection: admin_tags_path( format: :json ), sortable: true`
|
57
|
+
|
58
|
+
Example to update *position* field:
|
59
|
+
|
38
60
|
```rb
|
39
|
-
# Manually update position field
|
40
61
|
after_save :on_after_save
|
41
62
|
controller do
|
42
63
|
def on_after_save( object )
|
@@ -53,26 +74,28 @@ Example to enable JSON response on an ActiveAdmin model:
|
|
53
74
|
|
54
75
|
```rb
|
55
76
|
ActiveAdmin.register Tag do
|
56
|
-
config.per_page = 30
|
77
|
+
config.per_page = 30 # to limit served items
|
57
78
|
config.sort_order = 'name_asc'
|
58
79
|
index download_links: [:json]
|
59
80
|
end
|
60
81
|
```
|
61
82
|
|
62
|
-
##
|
83
|
+
## Example with select_one
|
63
84
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
85
|
+
In a form:
|
86
|
+
|
87
|
+
`f.input :article, as: :select_one, placeholder: 'Search...', remote_collection: admin_articles_path( format: :json ), search_param: 'title_contains', text_key: 'title'`
|
88
|
+
|
89
|
+
As filter:
|
90
|
+
|
91
|
+
`filter :article_id_eq, as: :select_one, filter_form: true, placeholder: 'Search...', search_param: 'title_contains', text_key: 'title', remote_collection: '/admin/articles.json'`
|
71
92
|
|
72
93
|
## Do you like it? Star it!
|
73
94
|
|
74
95
|
If you use this component just star it. A developer is more motivated to improve a project when there is some interest.
|
75
96
|
|
97
|
+
Take a look at [other ActiveAdmin components](https://github.com/blocknotes?utf8=✓&tab=repositories&q=activeadmin&type=source) that I made if you are curious.
|
98
|
+
|
76
99
|
## Contributors
|
77
100
|
|
78
101
|
- [Mattia Roccoberton](http://blocknot.es) - creator, maintainer
|
@@ -57,7 +57,7 @@ $(document).ready( function() {
|
|
57
57
|
$.ajax({
|
58
58
|
context: _this,
|
59
59
|
data: data,
|
60
|
-
url: $(this).data( 'remote' ),
|
60
|
+
url: $(this).data( 'remote-collection' ),
|
61
61
|
complete: function( req, status ) {
|
62
62
|
$(this).data( 'searching', '' );
|
63
63
|
},
|
@@ -74,7 +74,7 @@ $(document).ready( function() {
|
|
74
74
|
}, 400 );
|
75
75
|
|
76
76
|
$('.select_many.input .search-select').each( function() {
|
77
|
-
$(this).on( 'keyup', $(this).data( 'remote' ) ? onRemoteSelect : onLocalSelect );
|
77
|
+
$(this).on( 'keyup', $(this).data( 'remote-collection' ) ? onRemoteSelect : onLocalSelect );
|
78
78
|
});
|
79
79
|
$('.select_many .add').on( 'click', function() {
|
80
80
|
var select = $(this).parent().prev();
|
@@ -103,32 +103,32 @@ $(document).ready( function() {
|
|
103
103
|
}
|
104
104
|
});
|
105
105
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
106
|
+
var onRemoteSelectOne = smDebounce( function( event ) {
|
107
|
+
var select = $(this).next();
|
108
|
+
if( select.data( 'searching' ) != '1' ) {
|
109
|
+
select.data( 'searching', '1' );
|
110
|
+
var data = {}
|
111
|
+
var search_key = $(this).data('search') ? $(this).data('search') : 'name_contains';
|
112
|
+
var value_key = $(this).data('value') ? $(this).data('value') : 'id';
|
113
|
+
var text_key = $(this).data('text') ? $(this).data('text') : 'name';
|
114
|
+
data['q['+search_key+']'] = $(this).val();
|
115
|
+
$.ajax({
|
116
|
+
context: select,
|
117
|
+
data: data,
|
118
|
+
url: $(this).data( 'remote-collection' ),
|
119
|
+
complete: function( req, status ) {
|
120
|
+
$(this).data( 'searching', '' );
|
121
|
+
},
|
122
|
+
success: function( data, status, req ) {
|
123
|
+
var sel = $(this);
|
124
|
+
sel.empty();
|
125
|
+
data.forEach( function( item ) {
|
126
|
+
sel.append( $('<option>', { value: item[value_key], text: item[text_key] }) );
|
127
|
+
});
|
128
|
+
sel.parent().find( '.status' ).text( '[' + data.length + ']' );
|
129
|
+
},
|
130
|
+
});
|
131
|
+
}
|
132
|
+
}, 500 );
|
133
|
+
$('.select-one-inputs > .search-select').on( 'keyup', onRemoteSelectOne );
|
134
134
|
});
|
@@ -16,21 +16,39 @@ body.active_admin
|
|
16
16
|
display: none
|
17
17
|
&:hover
|
18
18
|
background: #e0e0e0
|
19
|
-
.
|
20
|
-
margin-bottom: 1px
|
21
|
-
.selects
|
19
|
+
.select-many-inputs
|
22
20
|
display: flex
|
23
21
|
flex-wrap: wrap
|
24
22
|
> input[type="text"], > select, > span
|
25
23
|
min-width: auto
|
26
|
-
width: calc(50% - 12px)
|
24
|
+
width: calc(50% - 12px)
|
27
25
|
> span
|
28
26
|
padding: 3px 6px 1px 6px
|
27
|
+
.empty
|
28
|
+
width: 20%
|
29
29
|
.available
|
30
30
|
font-style: italic
|
31
|
+
padding: 0 6px 0 6px
|
31
32
|
.selected
|
32
33
|
font-style: italic
|
33
|
-
padding
|
34
|
+
padding: 0 0 0 12px
|
35
|
+
.search-select
|
36
|
+
margin-bottom: 1px
|
37
|
+
width: 50%
|
34
38
|
[sortable] >.buttons
|
35
39
|
a.move_up, a.move_down
|
36
40
|
display: block
|
41
|
+
|
42
|
+
.formtastic > .inputs .select_one
|
43
|
+
.select-one-inputs
|
44
|
+
display: flex
|
45
|
+
> input[type="text"], > select, > span
|
46
|
+
display: inline-block
|
47
|
+
width: calc(50% - 12px)
|
48
|
+
.status
|
49
|
+
line-height: 26px
|
50
|
+
margin-left: 5px
|
51
|
+
|
52
|
+
.filter_form .select-one-inputs
|
53
|
+
.status
|
54
|
+
display: none
|
data/config/locales/en.yml
CHANGED
@@ -1,23 +1,20 @@
|
|
1
1
|
module Formtastic
|
2
2
|
module Inputs
|
3
3
|
class SelectManyInput < SelectInput
|
4
|
-
# def input_html_options
|
5
|
-
# super.merge( class: 'select-many' )
|
6
|
-
# end
|
7
|
-
|
8
4
|
def to_html
|
9
|
-
|
10
|
-
opts
|
5
|
+
options[:'data-remote-collection'] = options.delete( :remote_collection )
|
6
|
+
opts = { class: 'select-many-inputs' }
|
7
|
+
opts[:sortable] = options.delete( :sortable ) if options[:sortable]
|
11
8
|
input_wrapping do
|
12
9
|
label_html <<
|
13
|
-
hidden_input <<
|
14
10
|
template.content_tag( :div, opts ) do
|
15
|
-
|
16
|
-
|
17
|
-
template.content_tag( :span,
|
18
|
-
template.content_tag( :span, template.t( 'inputs.select_many.
|
19
|
-
|
20
|
-
|
11
|
+
hidden_input <<
|
12
|
+
search_box_html <<
|
13
|
+
template.content_tag( :span, '', class: 'empty' ) <<
|
14
|
+
template.content_tag( :span, template.t( 'inputs.select_many.available' ), class: 'available' ) <<
|
15
|
+
template.content_tag( :span, template.t( 'inputs.select_many.selected' ), class: 'selected' ) <<
|
16
|
+
select_src_html <<
|
17
|
+
buttons_html <<
|
21
18
|
select_dst_html
|
22
19
|
end
|
23
20
|
end
|
@@ -43,12 +40,12 @@ module Formtastic
|
|
43
40
|
end
|
44
41
|
|
45
42
|
def search_box_html
|
46
|
-
@opts ||= {id: nil, class: 'search-select', placeholder: options
|
43
|
+
@opts ||= {id: nil, class: 'search-select', placeholder: options.delete( :placeholder ), 'data-remote-collection': options[:'data-remote-collection'], 'data-search': options[:search_param] ? options[:search_param] : 'name_contains', 'data-text': options[:text_key] ? options[:text_key] : 'name', 'data-value': options[:value_key] ? options[:value_key] : 'id'}
|
47
44
|
template.text_field_tag( nil, '', @opts )
|
48
45
|
end
|
49
46
|
|
50
47
|
def select_src_html
|
51
|
-
coll = if options[:
|
48
|
+
coll = if options[:'data-remote-collection']
|
52
49
|
[]
|
53
50
|
else
|
54
51
|
# TODO: add option unique ?
|
@@ -5,66 +5,31 @@ module Formtastic
|
|
5
5
|
super.merge include_blank: false
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
def input_wrapping(&block)
|
9
|
+
template.content_tag(options[:filter_form] ? :div : :li,
|
10
|
+
[template.capture(&block), error_html, hint_html].join("\n").html_safe,
|
11
|
+
wrapper_html_options
|
12
|
+
)
|
13
|
+
end
|
12
14
|
|
13
15
|
def to_html
|
16
|
+
opts = { class: 'select-one-inputs' }
|
14
17
|
input_wrapping do
|
15
18
|
label_html <<
|
16
|
-
|
17
|
-
|
19
|
+
template.content_tag( :div, opts ) do
|
20
|
+
search_box <<
|
21
|
+
select_html <<
|
22
|
+
# template.content_tag( :span, '' ) <<
|
23
|
+
template.content_tag( :span, template.t( 'inputs.select_one.status' ), class: 'status' )
|
24
|
+
end
|
18
25
|
end
|
19
26
|
end
|
20
27
|
|
21
|
-
# def hidden_input
|
22
|
-
# template.content_tag( :div, class: 'values', 'data-name': input_html_options[:name] ) do
|
23
|
-
# values = object.send( input_name )
|
24
|
-
# values = [values] if values.is_a? Fixnum
|
25
|
-
# values.each do |value|
|
26
|
-
# template.concat template.hidden_field_tag( input_html_options[:name], value, {id: nil} )
|
27
|
-
# end if values
|
28
|
-
# end
|
29
|
-
# end
|
30
|
-
|
31
28
|
def search_box
|
32
|
-
@opts ||= {id: nil, class: 'search-select', placeholder: options[:placeholder], 'data-remote': options[:remote_collection], 'data-search': options[:search_param] ? options[:search_param] : 'name_contains', 'data-text': options[:text_key] ? options[:text_key] : 'name', 'data-value': options[:value_key] ? options[:value_key] : 'id'}
|
29
|
+
@opts ||= {id: nil, class: 'search-select', placeholder: options[:placeholder], 'data-remote-collection': options[:remote_collection], 'data-search': options[:search_param] ? options[:search_param] : 'name_contains', 'data-text': options[:text_key] ? options[:text_key] : 'name', 'data-value': options[:value_key] ? options[:value_key] : 'id', 'data-msg': options[:msg_items]}
|
33
30
|
template.text_field_tag( nil, '', @opts )
|
34
31
|
end
|
35
32
|
|
36
|
-
# def select_src_html
|
37
|
-
# coll = if options[:remote_collection]
|
38
|
-
# []
|
39
|
-
# else
|
40
|
-
# # TODO: add option unique ?
|
41
|
-
# selected = object.send( input_name )
|
42
|
-
# selected = [selected] if selected.is_a? Fixnum
|
43
|
-
# selected ? collection.select { |option| !selected.include?( option[1] ) } : collection
|
44
|
-
# end
|
45
|
-
# opts = input_options.dup.merge( name: nil, id: nil, multiple: true, 'data-select': 'src' )
|
46
|
-
# template.select_tag nil, template.options_for_select( coll ), opts
|
47
|
-
# end
|
48
|
-
|
49
|
-
|
50
|
-
# def select_dst_html
|
51
|
-
# selected = object.send( input_name )
|
52
|
-
# selected = [selected] if selected.is_a? Fixnum
|
53
|
-
# coll = selected ? collection.select { |option| selected.include?( option[1] ) } : collection
|
54
|
-
# opts = input_options.dup.merge( name: nil, id: nil, multiple: true, 'data-select': 'dst' )
|
55
|
-
# template.select_tag nil, template.options_for_select( coll ), opts
|
56
|
-
# end
|
57
|
-
|
58
|
-
# # def select_html
|
59
|
-
# # selected = object.send( input_name )
|
60
|
-
# # coll = collection.select { |option| selected.include?( option[1] ) }
|
61
|
-
|
62
|
-
# # opts = input_options.dup.merge( name: nil, id: nil, multiple: true )
|
63
|
-
# # template.select_tag nil, template.options_for_select( coll ), opts
|
64
|
-
|
65
|
-
# # # builder.select(input_name, coll, input_options, input_html_options)
|
66
|
-
# # end
|
67
|
-
|
68
33
|
def select_html
|
69
34
|
selected = object.send( input_name )
|
70
35
|
sel = ''
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activeadmin_select_many
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mattia Roccoberton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-09-
|
11
|
+
date: 2017-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activeadmin
|