effective_bootstrap 0.2.3 → 0.2.4

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: a9a6d951deeba08d113ac19bdfed662eb48c9692
4
- data.tar.gz: 6643a8efbff7d4d5fccf3fc12f3bd804eca6926d
3
+ metadata.gz: dbcd85ec412ee04f3a0cd56458b24a615c7bc0b7
4
+ data.tar.gz: a4e5e7b632e4d5c486e68fd3acabf6f1e72bcd61
5
5
  SHA512:
6
- metadata.gz: 42711f03fa1bbb0ca9d0225988f5c0ce707c04d6981077b7a31f1d9172ec3647561a99940382df742847976b4f4900745f5a921665bba38cc6521079ab4a5a55
7
- data.tar.gz: a2c063827ca23869e1c61bda8dd0d278aad5ab0bf101b2b6df5d79357cdf329f00cb0691694d517be5d362c01467c96a67214ffe42e4da6eec4aa03109c43f44
6
+ metadata.gz: e808ddfb63187adf28e906b459e2387b38f69f5d47c9f0b9bece57b588c8d47559693f413b9fcbc2df3fb731bd11c329f9602b5591c852e0744907f04bbe04ba
7
+ data.tar.gz: 132f566a29c1cb27b8bba080e8fa14c90dc15deb08d5df4c4ebb93f8e8933b814afd6b72f562d9dc8ccbb2af1c33096c5794084ad68130de1d8c00a9483baa97
data/README.md CHANGED
@@ -255,6 +255,18 @@ More info is available here:
255
255
 
256
256
  http://eonasdan.github.io/bootstrap-datetimepicker/Events/
257
257
 
258
+ ## Custom percent_field
259
+
260
+ This custom form input uses no 3rd party jQuery plugins.
261
+
262
+ It displays a percentage formatted value `100` or `12.500` but posts the "percentage as integer" value of `100000` or `12500` to the server.
263
+
264
+ It's like the price field, but 3 digits instead of 2.
265
+
266
+ ```haml
267
+ = f.percent_field :percent
268
+ ```
269
+
258
270
  ## Custom price_field
259
271
 
260
272
  This custom form input uses no 3rd party jQuery plugins.
@@ -8,6 +8,7 @@
8
8
  //= require ./effective_checks/input
9
9
  //= require ./effective_editor/input
10
10
  //= require ./effective_file/input
11
+ //= require ./effective_percent/input
11
12
  //= require ./effective_phone/input
12
13
  //= require ./effective_price/input
13
14
  //= require ./effective_radio/input
@@ -0,0 +1,39 @@
1
+ # Prevent non-currency buttons from being pressed
2
+ $(document).on 'keydown', "input[type='text'].effective_percent", (event) ->
3
+ allowed = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.']
4
+
5
+ if event.key && event.key.length == 1 && event.metaKey == false && allowed.indexOf(event.key) == -1
6
+ event.preventDefault()
7
+
8
+ # Assign the hidden input a value of 100x value
9
+ $(document).on 'change keyup', "input[type='text'].effective_percent", (event) ->
10
+ $input = $(event.target)
11
+ value = $input.val()
12
+
13
+ unless value == ''
14
+ value = (parseFloat(value || 0.00) * 1000.00).toFixed(0)
15
+
16
+ $input.siblings("input[type='hidden']").first().val(value)
17
+
18
+ # Format the value for display as percentage
19
+ $(document).on 'change', "input[type='text'].effective_percent", (event) ->
20
+ $input = $(event.target)
21
+ value = $input.siblings("input[type='hidden']").first().val()
22
+ max = 100000 # 100% is our max value
23
+
24
+ unless value == ''
25
+ value = parseInt(value || 0)
26
+
27
+ if value > max # 100% is our max value
28
+ value = max
29
+ $input.siblings("input[type='hidden']").first().val(max)
30
+
31
+ if value < -max # -100% is our min value
32
+ value = -max
33
+ $input.siblings("input[type='hidden']").first().val(-max)
34
+
35
+ value = (value / 1000.0) if value != 0
36
+ value = value.toFixed(3).toString()
37
+ value = value.replace('.000', '') if value.endsWith('.000')
38
+
39
+ $input.val(value)
@@ -0,0 +1 @@
1
+ //= require ./initialize
@@ -10,7 +10,7 @@ $(document).on 'change keyup', "input[type='text'].effective_price", (event) ->
10
10
  $input = $(event.target)
11
11
  value = $input.val().replace(/,/g, '')
12
12
 
13
- unless $input.data('include-blank') && value == ''
13
+ unless value == ''
14
14
  value = (parseFloat(value || 0.00) * 100.00).toFixed(0)
15
15
 
16
16
  $input.siblings("input[type='hidden']").first().val(value)
@@ -19,10 +19,19 @@ $(document).on 'change keyup', "input[type='text'].effective_price", (event) ->
19
19
  $(document).on 'change', "input[type='text'].effective_price", (event) ->
20
20
  $input = $(event.target)
21
21
  value = $input.siblings("input[type='hidden']").first().val()
22
+ max = 2000000000
22
23
 
23
- unless $input.data('include-blank') && value == ''
24
+ unless value == ''
24
25
  value = parseInt(value || 0)
25
26
 
27
+ if value > max # 20 million is our max value
28
+ value = max
29
+ $input.siblings("input[type='hidden']").first().val(max)
30
+
31
+ if value < -max # -20 million is our min value
32
+ value = -max
33
+ $input.siblings("input[type='hidden']").first().val(-max)
34
+
26
35
  if isNaN(value) == false && value != ''
27
36
  value = (value / 100.0) if value != 0
28
37
 
@@ -13,9 +13,11 @@ module EffectiveBootstrapHelper
13
13
  # variations can be :dropup, :dropleft, :dropright
14
14
  # split can be true, false
15
15
  # right is to right align things
16
- def dropdown(variation: nil, split: true, btn: 'btn-outline-primary', right: false, &block)
16
+ def dropdown(variation: nil, split: true, btn_class: nil, right: false, &block)
17
17
  raise 'expected a block' unless block_given?
18
18
 
19
+ btn_class = btn_class.presence || 'btn-outline-primary'
20
+
19
21
  @_dropdown_link_tos = []; yield
20
22
 
21
23
  return @_dropdown_link_tos.first if @_dropdown_link_tos.length <= 1
@@ -23,7 +25,7 @@ module EffectiveBootstrapHelper
23
25
  retval = if split
24
26
  first = @_dropdown_link_tos.first
25
27
  menu = content_tag(:div, @_dropdown_link_tos[1..-1].join.html_safe, class: ['dropdown-menu', ('dropdown-menu-right' if right)].compact.join(' '))
26
- split = content_tag(:button, class: "btn #{btn} dropdown-toggle dropdown-toggle-split", type: 'button', 'data-toggle': 'dropdown', 'aria-haspopup': true, 'aria-expanded': false) do
28
+ split = content_tag(:button, class: "btn #{btn_class} dropdown-toggle dropdown-toggle-split", type: 'button', 'data-toggle': 'dropdown', 'aria-haspopup': true, 'aria-expanded': false) do
27
29
  content_tag(:span, 'Toggle Dropdown', class: 'sr-only')
28
30
  end
29
31
 
@@ -59,15 +61,17 @@ module EffectiveBootstrapHelper
59
61
  concat link_to(label, path, options)
60
62
  end
61
63
 
62
- # # Works with dots do and dropdown do
64
+ # Works with dots do and dropdown do
63
65
  def dropdown_link_to(label, path, options = {})
66
+ btn_class = options.delete(:btn_class).presence || 'btn-outline-primary'
67
+
64
68
  unless @_dropdown_link_tos
65
69
  options[:class] = [options[:class], 'dropdown-item'].compact.join(' ')
66
70
  return link_to(label, path, options)
67
71
  end
68
72
 
69
73
  if @_dropdown_link_tos.length == 0
70
- options[:class] = [options[:class], 'btn btn-outline-primary'].compact.join(' ') unless options[:class].to_s.include?('btn-')
74
+ options[:class] = [options[:class], 'btn', btn_class].compact.join(' ')
71
75
  else
72
76
  options[:class] = [options[:class], 'dropdown-item'].compact.join(' ')
73
77
  end
@@ -77,6 +77,10 @@ module Effective
77
77
  Effective::FormInputs::PasswordField.new(name, options, builder: self).to_html { super(name, options) }
78
78
  end
79
79
 
80
+ def percent_field(name, options = {})
81
+ Effective::FormInputs::PercentField.new(name, options, builder: self).to_html
82
+ end
83
+
80
84
  def phone_field(name, options = {})
81
85
  Effective::FormInputs::PhoneField.new(name, options, builder: self).to_html { super(name, options) }
82
86
  end
@@ -36,8 +36,8 @@ module Effective
36
36
  include_blank = options[:input].delete(:include_blank)
37
37
 
38
38
  @collection_options = {
39
- checked: (checked || selected || passed_value || value),
40
- selected: (selected || checked || passed_value || value),
39
+ checked: (checked || selected || passed_value || polymorphic_value || value),
40
+ selected: (selected || checked || passed_value || polymorphic_value || value),
41
41
  include_blank: include_blank
42
42
  }.compact
43
43
  end
@@ -80,38 +80,36 @@ module Effective
80
80
  def assign_options_collection!
81
81
  collection = options[:input].delete(:collection)
82
82
 
83
- grouped = collection.kind_of?(Hash) && collection.values.all? { |v| v.respond_to?(:to_a) }
83
+ grouped = collection.kind_of?(Hash) && collection.values.first.respond_to?(:to_a)
84
84
 
85
85
  if grouped? && !grouped && collection.present?
86
86
  raise "Grouped collection expecting a Hash {'Posts' => Post.all, 'Events' => Event.all} or a Hash {'Posts' => [['Post A', 1], ['Post B', 2]], 'Events' => [['Event A', 1], ['Event B', 2]]}"
87
87
  end
88
88
 
89
- if grouped
90
- collection.transform_values! { |group| group.to_a }
89
+ if polymorphic? && !grouped && collection.present?
90
+ raise "Polymorphic collection expecting a Hash {'Posts' => Post.all, 'Events' => Event.all}"
91
91
  end
92
92
 
93
- if polymorphic?
94
- if grouped
95
- collection.values.each do |group|
96
- group.transform_values! { |obj| [obj.to_s, "#{obj.class.model_name}_#{obj.id}"] }
97
- end
93
+ @options_collection = (
94
+ if polymorphic?
95
+ collection.inject({}) { |h, (k, group)| h[k] = group.map { |obj| [obj.to_s, "#{obj.class.model_name}_#{obj.id}"] }; h }
96
+ elsif grouped
97
+ collection.inject({}) { |h, (k, group)| h[k] = group.map { |obj| obj }; h }
98
98
  else
99
- collection.transform_values! { |obj| [obj.to_s, "#{obj.class.model_name}_#{obj.id}"] }
99
+ collection.map { |obj| obj }
100
100
  end
101
- end
102
-
103
- @options_collection = collection.to_a
101
+ )
104
102
  end
105
103
 
106
104
  def assign_options_collection_methods!
107
105
  options[:input].reverse_merge!(
108
- if grouped? && polymorphic?
106
+ if polymorphic?
109
107
  { group_method: :last, group_label_method: :first, option_key_method: :second, option_value_method: :first }
110
108
  elsif grouped?
111
109
  { group_method: :last, group_label_method: :first, option_key_method: :second, option_value_method: :first }
112
- elsif options_collection[0].kind_of?(Array)
110
+ elsif options_collection.first.kind_of?(Array)
113
111
  { label_method: :first, value_method: :second }
114
- elsif options_collection[0].kind_of?(ActiveRecord::Base)
112
+ elsif options_collection.first.kind_of?(ActiveRecord::Base)
115
113
  { label_method: :to_s, value_method: :id }
116
114
  else
117
115
  { label_method: :to_s, value_method: :to_s }
@@ -128,7 +126,8 @@ module Effective
128
126
  end
129
127
 
130
128
  def polymorphic_value
131
- "#{object.class.model_name}_#{object.id}" if object
129
+ return nil unless polymorphic?
130
+ "#{value.class.model_name}_#{value.id}" if value
132
131
  end
133
132
 
134
133
  def polymorphic_type_value
@@ -10,7 +10,7 @@ module Effective
10
10
  end
11
11
 
12
12
  def input_js_options
13
- { format: format, showTodayButton: false, showClear: false, useCurrent: 'hour', disabledDates: disabled_dates.presence }.compact
13
+ { format: format, showTodayButton: false, showClear: false, useCurrent: 'hour', disabledDates: disabled_dates.presence, minDate: min_date.presence, maxDate: max_date.presence }.compact
14
14
  end
15
15
 
16
16
  def input_group_options
@@ -10,7 +10,7 @@ module Effective
10
10
  end
11
11
 
12
12
  def input_js_options
13
- { format: format, sideBySide: true, showTodayButton: false, showClear: false, useCurrent: 'hour', disabledDates: disabled_dates.presence }.compact
13
+ { format: format, sideBySide: true, showTodayButton: false, showClear: false, useCurrent: 'hour', disabledDates: disabled_dates.presence, minDate: min_date.presence, maxDate: max_date.presence }.compact
14
14
  end
15
15
 
16
16
  def input_group_options
@@ -37,22 +37,17 @@ module Effective
37
37
 
38
38
  def disabled_dates
39
39
  return @disabled_dates unless @disabled_dates.nil?
40
+ @disabled_dates ||= Array(options.delete(:disabledDates)).map { |obj| parse_object_date(obj) }.flatten.compact
41
+ end
42
+
43
+ def max_date
44
+ return @max_date unless @max_date.nil?
45
+ @max_date = parse_object_date(options.delete(:maxDate))
46
+ end
40
47
 
41
- @disabled_dates ||= (
42
- Array(options.delete(:disabledDates)).map do |obj|
43
- if obj.respond_to?(:strftime)
44
- obj.strftime('%F')
45
- elsif obj.kind_of?(Range) && obj.first.respond_to?(:strftime)
46
- [obj.first].tap do |dates|
47
- dates << (dates.last + 1.day) until (dates.last + 1.day) > obj.last
48
- end
49
- elsif obj.kind_of?(String)
50
- obj
51
- else
52
- raise 'unexpected disabledDates data. Expected a DateTime, Range of DateTimes or String'
53
- end
54
- end.flatten.compact
55
- )
48
+ def min_date
49
+ return @min_date unless @min_date.nil?
50
+ @min_date = parse_object_date(options.delete(:minDate))
56
51
  end
57
52
 
58
53
  def not_date_linked?
@@ -65,6 +60,24 @@ module Effective
65
60
  @am_pm = options.key?(:am_pm) ? options.delete(:am_pm) : true
66
61
  end
67
62
 
63
+ private
64
+
65
+ def parse_object_date(obj)
66
+ if obj.respond_to?(:strftime)
67
+ obj.strftime('%F')
68
+ elsif obj.kind_of?(Range) && obj.first.respond_to?(:strftime)
69
+ [obj.first].tap do |dates|
70
+ dates << (dates.last + 1.day) until (dates.last + 1.day) > obj.last
71
+ end
72
+ elsif obj.kind_of?(String)
73
+ obj
74
+ elsif obj.nil?
75
+ obj
76
+ else
77
+ raise "unexpected date object #{obj}. Expected a DateTime, Range of DateTimes or String"
78
+ end
79
+ end
80
+
68
81
  end
69
82
  end
70
83
  end
@@ -0,0 +1,33 @@
1
+ module Effective
2
+ module FormInputs
3
+ class PercentField < Effective::FormInput
4
+
5
+ def build_input(&block)
6
+ @builder.hidden_field(name, value: percent_to_integer, id: tag_id + '_value_as_integer') +
7
+ @template.text_field_tag(name, percent_to_s, options[:input].merge(id: tag_id, name: nil))
8
+ end
9
+
10
+ def input_group_options
11
+ { input_group: { class: 'input-group' }, prepend: content_tag(:span, icon('percent'), class: 'input-group-text') }
12
+ end
13
+
14
+ def input_html_options
15
+ { class: 'form-control effective_percent', autocomplete: 'off' }
16
+ end
17
+
18
+ private
19
+
20
+ def percent_to_integer
21
+ return nil unless value
22
+ value.kind_of?(Integer) ? value : ('%.3f' % (value / 1000.0))
23
+ end
24
+
25
+ def percent_to_s
26
+ return nil unless value
27
+ str = value.kind_of?(Integer) ? ('%.3f' % (value / 1000.0)) : value.to_s
28
+ str.gsub('.000', '')
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -12,26 +12,21 @@ module Effective
12
12
  end
13
13
 
14
14
  def input_html_options
15
- { class: 'form-control effective_price', maxlength: 14, autocomplete: 'off', 'data-include-blank': include_blank? }
15
+ { class: 'form-control effective_price', autocomplete: 'off' }
16
16
  end
17
17
 
18
18
  private
19
19
 
20
20
  def price
21
- return (include_blank? ? 0 : nil) unless value
21
+ return nil unless value
22
22
  value.kind_of?(Integer) ? value : ('%.2f' % (value / 100.0))
23
23
  end
24
24
 
25
25
  def currency
26
- return (include_blank? ? 0.00 : nil) unless value
26
+ return nil unless value
27
27
  value.kind_of?(Integer) ? ('%.2f' % (value / 100.0)) : value
28
28
  end
29
29
 
30
- def include_blank? # default false
31
- return @include_blank unless @include_blank.nil?
32
- @include_blank = (options.delete(:include_blank) || false)
33
- end
34
-
35
30
  end
36
31
  end
37
32
  end
@@ -5,9 +5,10 @@
5
5
  module Effective
6
6
  module FormInputs
7
7
  class Select < CollectionInput
8
+ INCLUDE_NULL = 'Blank (null)'
8
9
 
9
10
  def build_input(&block)
10
- html = if grouped? && polymorphic?
11
+ html = if polymorphic?
11
12
  @builder.grouped_collection_select(polymorphic_id_method, options_collection, group_method, group_label_method, option_key_method, option_value_method, collection_options, html_options)
12
13
  elsif grouped?
13
14
  @builder.grouped_collection_select(name, options_collection, group_method, group_label_method, option_key_method, option_value_method, collection_options, html_options)
@@ -32,7 +33,7 @@ module Effective
32
33
  theme: 'bootstrap',
33
34
  minimumResultsForSearch: 6,
34
35
  width: 'style',
35
- placeholder: (input_html_options.delete(:placeholder) || 'Please choose'),
36
+ placeholder: (options.delete(:placeholder) || 'Please choose'),
36
37
  allowClear: (true if include_blank?),
37
38
  tokenSeparators: ([',', ';', '\n', '\t'] if tags?),
38
39
  tags: (true if tags?),
@@ -47,16 +48,50 @@ module Effective
47
48
  'effective_select',
48
49
  'form-control',
49
50
  ('polymorphic' if polymorphic?),
50
- ('grouped' if grouped?),
51
+ ('grouped' if (grouped? || polymorphic?)),
51
52
  ('hide-disabled' if hide_disabled?),
52
53
  ('tags-input' if tags?),
53
54
  ].compact.join(' ')
54
55
 
55
- { class: classes, multiple: (true if multiple?), include_blank: (true if include_blank?) }.compact
56
+ { class: classes, multiple: (true if multiple?), include_blank: (true if include_blank?), include_null: include_null }.compact
57
+ end
58
+
59
+ def assign_options_collection!
60
+ super
61
+
62
+ return unless include_null
63
+
64
+ # Check for singles - transform the array
65
+ if options_collection.kind_of?(Array) && !options_collection.first.respond_to?(:to_a) # [:admin, :member]
66
+ @options_collection = options_collection.map { |obj| [obj, obj] }
67
+ end
68
+
69
+ if options_collection.kind_of?(Array) && options_collection.first.respond_to?(:to_a)
70
+ options_collection.push(['---------------', '-', disabled: 'disabled'])
71
+ options_collection.push([include_null, 'nil'])
72
+ end
73
+
74
+ if options_collection.kind_of?(Hash)
75
+ options_collection[include_null] = [[include_null, 'nil']]
76
+ end
77
+
78
+ options_collection
56
79
  end
57
80
 
58
81
  private
59
82
 
83
+ def include_null
84
+ return @include_null unless @include_null.nil?
85
+
86
+ obj = options.delete(:include_null)
87
+
88
+ @include_null = case obj
89
+ when nil then false
90
+ when true then INCLUDE_NULL
91
+ else obj
92
+ end
93
+ end
94
+
60
95
  def include_blank?
61
96
  return @include_blank unless @include_blank.nil?
62
97
  @include_blank = (options.key?(:include_blank) ? options.delete(:include_blank) : true) && !multiple?
@@ -1,3 +1,3 @@
1
1
  module EffectiveBootstrap
2
- VERSION = '0.2.3'.freeze
2
+ VERSION = '0.2.4'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_bootstrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-22 00:00:00.000000000 Z
11
+ date: 2018-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -387,6 +387,8 @@ files:
387
387
  - app/assets/javascripts/effective_editor/quill.js
388
388
  - app/assets/javascripts/effective_file/initialize.js.coffee
389
389
  - app/assets/javascripts/effective_file/input.js
390
+ - app/assets/javascripts/effective_percent/initialize.js.coffee
391
+ - app/assets/javascripts/effective_percent/input.js
390
392
  - app/assets/javascripts/effective_phone/initialize.js.coffee
391
393
  - app/assets/javascripts/effective_phone/input.js
392
394
  - app/assets/javascripts/effective_phone/jquery.maskedInput.js
@@ -440,6 +442,7 @@ files:
440
442
  - app/models/effective/form_inputs/form_group.rb
441
443
  - app/models/effective/form_inputs/number_field.rb
442
444
  - app/models/effective/form_inputs/password_field.rb
445
+ - app/models/effective/form_inputs/percent_field.rb
443
446
  - app/models/effective/form_inputs/phone_field.rb
444
447
  - app/models/effective/form_inputs/price_field.rb
445
448
  - app/models/effective/form_inputs/radios.rb