effective_bootstrap 0.2.3 → 0.2.4

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