activeadmin_select_many 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b9189e230febfc9cfdbc1088a2638db3417c06d5
4
- data.tar.gz: a48dfe85a5f14c48c42e71bc7666cc4624aef51a
3
+ metadata.gz: 71d37813e5fbcb67285b3c1e8413e4880e79e69d
4
+ data.tar.gz: 8cb5b71339e21a10c4e84cc059f0d5ede4e2a220
5
5
  SHA512:
6
- metadata.gz: bdcb42047d735858663d7deac41e3af1520a3ae2a08dffbb4395a2fe72590a4a21df8b6fea36b3436a2aac3058c7810105397bbf404bfae580f5be0491e9baca
7
- data.tar.gz: 591ea68f17f0ef69cd3dfbb151f296f75724d77e934995e5059953eb6b13007aa91b5b3d9817981cb8aa5a18aae90f2b978443ee300b35e6a0185e5839fef23b
6
+ metadata.gz: b8eabd05b1cf8e08453b0e5ae2bf169c11840f61bbc7066787c15725dcc943df14cdaea09e63de3577a8f4efe550f09f31790bdfc94066db862808b035421714
7
+ data.tar.gz: a4c4220404ecc844dade9e6b2656c15792315bb1cc2e639a6d4515ad722d27223f45621c4dc69e7baf72876dd7d8103fdfe4f0f2a4ef4593214709c934f4f9f8
data/README.md CHANGED
@@ -8,6 +8,7 @@ Features:
8
8
  - select on the right with selected items
9
9
  - local/remote collections
10
10
  - double click to add/remove items
11
+ - sortable
11
12
 
12
13
  ![screenshot](screenshot.png)
13
14
 
@@ -30,8 +31,33 @@ Add to ActiveAdmin model config, in *form* block.
30
31
  `f.input :sections, as: :select_many`
31
32
  - Remote collection (using AJAX):
32
33
  `f.input :tags, as: :select_many, remote_collection: admin_tags_path( format: :json )`
33
- - Changing search param and text key:
34
+ - Changing search param and text key (default: *name*):
34
35
  `f.input :tags, as: :select_many, remote_collection: admin_tags_path( format: :json ), search_param: 'category_contains', text_key: 'category', placeholder: 'Type something...'`
36
+ - Sortable (items position must be saved manually):
37
+ `f.input :tags, as: :select_many, remote_collection: admin_tags_path( format: :json ), sortable: true`
38
+ ```rb
39
+ # Manually update position field
40
+ after_save :on_after_save
41
+ controller do
42
+ def on_after_save( object )
43
+ if params[:article][:section_ids]
44
+ order = {}
45
+ params[:article][:section_ids].each_with_index { |id, i| order[id.to_i] = i }
46
+ object.sections.each { |item| item.update_column( :position, order[item.id].to_i ) }
47
+ end
48
+ end
49
+ end
50
+ ```
51
+
52
+ Example to enable JSON response on an ActiveAdmin model:
53
+
54
+ ```rb
55
+ ActiveAdmin.register Tag do
56
+ config.per_page = 30
57
+ config.sort_order = 'name_asc'
58
+ index download_links: [:json]
59
+ end
60
+ ```
35
61
 
36
62
  ## Options
37
63
 
@@ -39,6 +65,8 @@ Add to ActiveAdmin model config, in *form* block.
39
65
  - **placeholder**: placeholder string for search box
40
66
  - **remote_collection**: JSON path
41
67
  - **search_param**: parameter to use as search key (ransack style)
68
+ - **sortable**: set to true to enable sortable buttons (default: not set)
69
+ - **size**: number of rows of both the selects (default: 4)
42
70
  - **text_key**: key to use as text for select options
43
71
 
44
72
  ## Do you like it? Star it!
@@ -1,4 +1,4 @@
1
- function debounce( func, wait, immediate ) {
1
+ function smDebounce( func, wait, immediate ) {
2
2
  var timeout;
3
3
  return function() {
4
4
  var context = this, args = arguments;
@@ -13,6 +13,14 @@ function debounce( func, wait, immediate ) {
13
13
  };
14
14
  };
15
15
 
16
+ function smUpdateValues( parent ) {
17
+ var values = parent.find( '.values' );
18
+ values.empty();
19
+ parent.find( '[data-select="dst"] option' ).each( function() {
20
+ values.append( $('<input>', { type: 'hidden', name: values.data( 'name' ), value: $(this).val() }) );
21
+ });
22
+ }
23
+
16
24
  $(document).ready( function() {
17
25
  $('.select_many.input select').on( 'dblclick', function( event ) {
18
26
  if( event.target.tagName.toLowerCase() == 'option' ) {
@@ -21,22 +29,17 @@ $(document).ready( function() {
21
29
  var dst = parent.find( $(this).data( 'select' ) == 'src' ? '[data-select="dst"]' : '[data-select="src"]' );
22
30
  dst.append( $('<option>', { value: opt.val(), text: opt.text() }) );
23
31
  opt.remove();
24
- // Update values list
25
- var values = parent.find( '.values' );
26
- values.empty();
27
- parent.find( '[data-select="dst"] option' ).each( function() {
28
- values.append( $('<input>', { type: 'hidden', name: values.data( 'name' ), value: $(this).val() }) );
29
- });
32
+ smUpdateValues( parent );
30
33
  }
31
34
  });
32
35
 
33
- var onLocalSelect = debounce( function() {
36
+ var onLocalSelect = smDebounce( function() {
34
37
  var search = $(this).val().toLowerCase();
35
38
  $(this).closest( '.select_many' ).find( '[data-select="src"] option' ).each( function() {
36
39
  $(this).toggle( $(this).text().toLowerCase().indexOf( search ) >= 0 );
37
40
  });
38
41
  }, 250 );
39
- var onRemoteSelect = debounce( function() {
42
+ var onRemoteSelect = smDebounce( function() {
40
43
  var search = $(this).val().trim();
41
44
  if( search != '' && $(this).data( 'searching' ) != '1' ) {
42
45
  $(this).data( 'searching', '1' );
@@ -64,9 +67,52 @@ $(document).ready( function() {
64
67
  }
65
68
  }, 400 );
66
69
 
67
- // $('.select_many.input .search-select').on( 'keyup', onKeyup );
68
-
69
70
  $('.select_many.input .search-select').each( function() {
70
71
  $(this).on( 'keyup', $(this).data( 'remote' ) ? onRemoteSelect : onLocalSelect );
71
72
  });
73
+ $('.select_many [sortable] .move_up').on( 'click', function() {
74
+ var select = $(this).parent().next();
75
+ var current = select.find( 'option:selected' )[0];
76
+ if( current ) {
77
+ $(current).prev().before( current );
78
+ smUpdateValues( $(this).closest( '.select_many' ) );
79
+ }
80
+ });
81
+ $('.select_many [sortable] .move_down').on( 'click', function() {
82
+ var select = $(this).parent().next();
83
+ var current = select.find( 'option:selected' )[0];
84
+ if( current ) {
85
+ $(current).next().after( current );
86
+ smUpdateValues( $(this).closest( '.select_many' ) );
87
+ }
88
+ });
89
+
90
+ // // WORK IN PROGRESS
91
+ // var onRemoteSelectOne = smDebounce( function( event ) {
92
+ // if( $(this).data( 'searching' ) != '1' ) {
93
+ // $(this).data( 'searching', '1' );
94
+ // var _this = $(this);
95
+ // var data = {}
96
+ // var search_key = $(this).data('search') ? $(this).data('search') : 'name_contains';
97
+ // var value_key = $(this).data('value') ? $(this).data('value') : 'id';
98
+ // var text_key = $(this).data('text') ? $(this).data('text') : 'name';
99
+ // data['q['+search_key+']'] = event.key;
100
+ // $.ajax({
101
+ // context: _this,
102
+ // data: data,
103
+ // url: $(this).data( 'remote' ),
104
+ // complete: function( req, status ) {
105
+ // $(this).data( 'searching', '' );
106
+ // },
107
+ // success: function( data, status, req ) {
108
+ // var select = $(this);
109
+ // select.empty();
110
+ // data.forEach( function( item ) {
111
+ // select.append( $('<option>', { value: item[value_key], text: item[text_key] }) );
112
+ // });
113
+ // },
114
+ // });
115
+ // }
116
+ // }, 400 );
117
+ // $('.select_one.input select').on( 'keyup', onRemoteSelectOne );
72
118
  });
@@ -1,13 +1,30 @@
1
1
  body.active_admin
2
2
  .select_many
3
+ .buttons
4
+ margin-top: 0
5
+ text-align: center
6
+ width: 22px
7
+ a
8
+ background: #ddd
9
+ border: 1px solid #eee
10
+ display: block
11
+ font-weight: bold
12
+ line-height: 16px
13
+ text-decoration: none
14
+ &.move_up, &.move_down
15
+ display: none
3
16
  .search-select
4
17
  margin-bottom: 1px
5
18
  .selects
6
19
  display: flex
7
20
  flex-wrap: wrap
8
21
  > input[type="text"], > select, > span
9
- margin-right: 1%
10
22
  min-width: auto
11
- width: 49%
23
+ width: calc(50% - 12px);
12
24
  > span
13
25
  padding: 3px 6px 1px 6px
26
+ .selected
27
+ padding-left: 30px
28
+ [sortable] >.buttons
29
+ a.move_up, a.move_down
30
+ display: block
@@ -1,5 +1,5 @@
1
1
  module ActiveAdmin
2
2
  module SelectMany
3
- VERSION = '0.1.0'
3
+ VERSION = '0.1.2'
4
4
  end
5
5
  end
@@ -1,3 +1,4 @@
1
1
  require 'activeadmin/select_many'
2
2
 
3
3
  require 'formtastic/inputs/select_many_input'
4
+ # require 'formtastic/inputs/select_one_input' # WORK IN PROGRESS
@@ -6,15 +6,18 @@ module Formtastic
6
6
  # end
7
7
 
8
8
  def to_html
9
+ opts = { class: 'selects' }
10
+ opts[:sortable] = options[:sortable] if options[:sortable]
9
11
  input_wrapping do
10
12
  label_html <<
11
13
  hidden_input <<
12
- template.content_tag( :div, class: 'selects' ) do
13
- search_box +
14
+ template.content_tag( :div, opts ) do
15
+ search_box_html +
14
16
  template.content_tag( :span, '' ) +
15
- template.content_tag( :span, template.t( 'inputs.select_many.available' ) + ':' ) +
16
- template.content_tag( :span, template.t( 'inputs.select_many.selected' ) + ':' ) +
17
+ template.content_tag( :span, template.t( 'inputs.select_many.available' ), class: 'available' ) +
18
+ template.content_tag( :span, template.t( 'inputs.select_many.selected' ), class: 'selected' ) +
17
19
  select_src_html +
20
+ buttons_html +
18
21
  select_dst_html
19
22
  end
20
23
  end
@@ -22,13 +25,24 @@ module Formtastic
22
25
 
23
26
  def hidden_input
24
27
  template.content_tag( :div, class: 'values', 'data-name': input_html_options[:name] ) do
25
- object.send( input_name ).each do |value|
28
+ values = object.send( input_name )
29
+ values = [values] if values.is_a? Fixnum
30
+ values.each do |value|
26
31
  template.concat template.hidden_field_tag( input_html_options[:name], value, {id: nil} )
27
- end
32
+ end if values
33
+ end
34
+ end
35
+
36
+ def buttons_html
37
+ template.content_tag( :div, class: 'buttons' ) do
38
+ # template.link_to( '&rarr;'.html_safe, 'Javascript:void(0)', class: 'add' ) +
39
+ # template.link_to( '&larr;'.html_safe, 'Javascript:void(0)', class: 'remove' ) +
40
+ template.link_to( '&uarr;'.html_safe, 'Javascript:void(0)', class: 'move_up' ) +
41
+ template.link_to( '&darr;'.html_safe, 'Javascript:void(0)', class: 'move_down' )
28
42
  end
29
43
  end
30
44
 
31
- def search_box
45
+ def search_box_html
32
46
  @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'}
33
47
  template.text_field_tag( nil, '', @opts )
34
48
  end
@@ -39,28 +53,20 @@ module Formtastic
39
53
  else
40
54
  # TODO: add option unique ?
41
55
  selected = object.send( input_name )
42
- collection.select { |option| !selected.include?( option[1] ) }
56
+ selected = [selected] if selected.is_a? Fixnum
57
+ selected ? collection.select { |option| !selected.include?( option[1] ) } : collection
43
58
  end
44
- opts = input_options.dup.merge( name: nil, id: nil, multiple: true, 'data-select': 'src' )
59
+ opts = input_options.merge( name: nil, id: nil, multiple: true, 'data-select': 'src', size: options[:size] ? options[:size] : 4 )
45
60
  template.select_tag nil, template.options_for_select( coll ), opts
46
61
  end
47
62
 
48
63
  def select_dst_html
49
64
  selected = object.send( input_name )
50
- coll = collection.select { |option| selected.include?( option[1] ) }
51
- opts = input_options.dup.merge( name: nil, id: nil, multiple: true, 'data-select': 'dst' )
65
+ selected = [selected] if selected.is_a? Fixnum
66
+ coll = selected ? collection.select { |option| selected.include?( option[1] ) } : collection
67
+ opts = input_options.merge( name: nil, id: nil, multiple: true, 'data-select': 'dst', size: options[:size] ? options[:size] : 4 )
52
68
  template.select_tag nil, template.options_for_select( coll ), opts
53
69
  end
54
-
55
- # def select_html
56
- # selected = object.send( input_name )
57
- # coll = collection.select { |option| selected.include?( option[1] ) }
58
-
59
- # opts = input_options.dup.merge( name: nil, id: nil, multiple: true )
60
- # template.select_tag nil, template.options_for_select( coll ), opts
61
-
62
- # # builder.select(input_name, coll, input_options, input_html_options)
63
- # end
64
70
  end
65
71
  end
66
72
  end
@@ -0,0 +1,81 @@
1
+ module Formtastic
2
+ module Inputs
3
+ class SelectOneInput < SelectInput
4
+ def input_options
5
+ super.merge include_blank: false
6
+ end
7
+
8
+ # def input_html_options
9
+ # super.merge( class: 'select-one' )
10
+ # end
11
+
12
+
13
+ def to_html
14
+ input_wrapping do
15
+ label_html <<
16
+ search_box <<
17
+ select_html
18
+ end
19
+ end
20
+
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
+ 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'}
33
+ template.text_field_tag( nil, '', @opts )
34
+ end
35
+
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
+ def select_html
69
+ selected = object.send( input_name )
70
+ sel = ''
71
+ collection.each do |item|
72
+ if item[1] == selected
73
+ sel = template.options_for_select( [item], selected ) if item[1] == selected
74
+ break
75
+ end
76
+ end
77
+ builder.select(input_name, sel, input_options, input_html_options.merge( 'data-select': 'src' ) )
78
+ end
79
+ end
80
+ end
81
+ end
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.1.0
4
+ version: 0.1.2
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-18 00:00:00.000000000 Z
11
+ date: 2017-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeadmin
@@ -45,6 +45,7 @@ files:
45
45
  - lib/activeadmin/select_many/version.rb
46
46
  - lib/activeadmin_select_many.rb
47
47
  - lib/formtastic/inputs/select_many_input.rb
48
+ - lib/formtastic/inputs/select_one_input.rb
48
49
  - screenshot.png
49
50
  homepage: https://github.com/blocknotes/activeadmin_select_many
50
51
  licenses: