activeadmin_select_many 0.1.0 → 0.1.2

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 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: