effective_bootstrap 0.9.6 → 0.9.11

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.
@@ -3,6 +3,7 @@
3
3
  @import 'effective_checks/input';
4
4
  @import 'effective_datetime/input';
5
5
  @import 'effective_file/input';
6
+ @import 'effective_has_many/input';
6
7
  @import 'effective_rich_text_area/input';
7
8
  @import 'effective_select/input';
8
9
  @import 'effective_select_or_text/input';
@@ -4,5 +4,5 @@
4
4
  font-size: 0.75rem;
5
5
  margin: 0.25rem 0;
6
6
 
7
- a { padding: 0.25rem 0; }
7
+ a { padding: 0.5rem 0; }
8
8
  }
@@ -0,0 +1,42 @@
1
+ body.dragging,
2
+ body.dragging * {
3
+ cursor: move !important;
4
+ }
5
+
6
+ .form-has-many {
7
+ .has-many-placeholder {
8
+ position: relative;
9
+ height: 2rem;
10
+
11
+ &:before {
12
+ position: absolute;
13
+ content: '';
14
+ background-image: asset-data-url('icons/arrow-right-circle.svg');
15
+ background-repeat: no-repeat;
16
+ height: 2rem;
17
+ width: 2rem;
18
+ }
19
+ }
20
+
21
+ .has-many-fields.dragged {
22
+ position: absolute;
23
+ opacity: 0;
24
+ z-index: 2000;
25
+ .has-many-move { display: none; }
26
+ }
27
+
28
+ .has-many-move svg { margin-top: 6px; }
29
+ .has-many-move { display: none; }
30
+
31
+ .has-many-remove { margin-top: 1.5rem; }
32
+ .has-many-move { margin-top: 1.5rem; }
33
+ }
34
+
35
+ .form-has-many.reordering {
36
+ .has-many-move { display: inline-block; }
37
+ }
38
+
39
+ .form-has-many.tight {
40
+ .has-many-remove { margin-top: 0; }
41
+ .has-many-move { margin-top: 0; }
42
+ }
@@ -2,7 +2,7 @@ module EffectiveFormBuilderHelper
2
2
  def effective_form_with(**options, &block)
3
3
  # Compute the default ID
4
4
  subject = Array(options[:scope] || options[:model]).last
5
- class_name = subject.class.name.underscore
5
+ class_name = subject.class.name.parameterize.underscore
6
6
  unique_id = options.except(:model).hash.abs
7
7
 
8
8
  html_id = if subject.kind_of?(Symbol)
@@ -56,6 +56,17 @@ module EffectiveFormBuilderHelper
56
56
  # Assign default ID
57
57
  options[:id] ||= (options[:html].delete(:id) || html_id) unless options.key?(:id)
58
58
 
59
+ # Assign url if engine present
60
+ options[:url] ||= if options[:engine] && options[:model].present?
61
+ resource = Effective::Resource.new(options[:model])
62
+
63
+ if subject.respond_to?(:persisted?) && subject.persisted?
64
+ resource.action_path(:update, subject)
65
+ elsif subject.respond_to?(:new_record?) && subject.new_record?
66
+ resource.action_path(:create)
67
+ end
68
+ end
69
+
59
70
  without_error_proc do
60
71
  form_with(**options.merge(builder: Effective::FormBuilder), &block)
61
72
  end
@@ -203,5 +203,19 @@ module Effective
203
203
  Effective::FormLogics::ShowIfAny.new(*args, builder: self).to_html(&block)
204
204
  end
205
205
 
206
+ # Has Many
207
+ def has_many(name, collection = nil, options = {}, &block)
208
+ association = object.class.reflect_on_all_associations.find { |a| a.name == name }
209
+ raise("expected #{object.class.name} to has_many :#{name}") if association.blank?
210
+
211
+ nested_attributes_options = (object.class.nested_attributes_options || {})[name]
212
+ raise("expected #{object.class.name} to accepts_nested_attributes_for :#{name}") if nested_attributes_options.blank?
213
+
214
+ options = collection if collection.kind_of?(Hash)
215
+ options.merge!(collection: collection) if collection && !collection.kind_of?(Hash)
216
+
217
+ Effective::FormInputs::HasMany.new(name, options, builder: self).to_html(&block)
218
+ end
219
+
206
220
  end
207
221
  end
@@ -50,18 +50,29 @@ module Effective
50
50
  end
51
51
 
52
52
  def build_label
53
- return BLANK if options[:label] == false
53
+ return BLANK if options[:label] == false && !actions?
54
54
  return BLANK if name.kind_of?(NilClass)
55
55
 
56
- text = (options[:label].delete(:text) || (object.class.human_attribute_name(name) if object) || BLANK).html_safe
56
+ text = begin
57
+ if options[:label] == false
58
+ nil
59
+ elsif options[:label].key?(:text)
60
+ options[:label].delete(:text)
61
+ elsif object.present?
62
+ object.class.human_attribute_name(name)
63
+ end || BLANK
64
+ end.html_safe
65
+
66
+ actions = if !disabled? && actions?
67
+ content_tag(:div, class: 'effective-checks-actions text-muted') do
68
+ link_to('Select All', '#', 'data-effective-checks-all': true) + ' - ' + link_to('Select None', '#', 'data-effective-checks-none': true)
69
+ end
70
+ end
57
71
 
58
72
  content_tag(:label, options[:label]) do
59
- text + content_tag(:div, class: 'effective-checks-actions text-muted') do
60
- unless disabled? || !actions?
61
- link_to('Select All', '#', 'data-effective-checks-all': true) + ' - ' + link_to('Select None', '#', 'data-effective-checks-none': true)
62
- end
63
- end
73
+ [text, actions].compact.join.html_safe
64
74
  end
75
+
65
76
  end
66
77
 
67
78
  def build_item(builder)
@@ -91,7 +102,7 @@ module Effective
91
102
 
92
103
  def actions? # default true
93
104
  return @actions unless @actions.nil?
94
- @actions = (options.delete(:actions) != false)
105
+ @actions = (options[:input].delete(:actions) != false)
95
106
  end
96
107
 
97
108
  end
@@ -15,11 +15,12 @@ module Effective
15
15
 
16
16
  def input_js_options
17
17
  {
18
- modules: {
19
- toolbar: toolbar,
20
- imageResize: imageResize,
18
+ modules: {
19
+ toolbar: toolbar,
20
+ imageResize: imageResize,
21
21
  imageDropAndPaste: imageDropAndPaste,
22
- syntax: (content_mode == :code)
22
+ magicUrl: magicUrl,
23
+ syntax: (content_mode == :code)
23
24
  },
24
25
  theme: 'snow',
25
26
  placeholder: "Add #{name.to_s.pluralize}...",
@@ -42,7 +43,7 @@ module Effective
42
43
  end
43
44
 
44
45
  def imageDropAndPaste
45
- active_storage && !(content_mode == :code)
46
+ active_storage && !(content_mode == :code)
46
47
  end
47
48
 
48
49
  def imageResize
@@ -57,6 +58,10 @@ module Effective
57
58
  }
58
59
  end
59
60
 
61
+ def magicUrl
62
+ true
63
+ end
64
+
60
65
  def active_storage
61
66
  return @active_storage unless @active_storage.nil?
62
67
 
@@ -0,0 +1,207 @@
1
+ module Effective
2
+ module FormInputs
3
+ class HasMany < Effective::FormInput
4
+ BLANK = ''.html_safe
5
+
6
+ def to_html(&block)
7
+ object.send(name).build() if build? && collection.blank?
8
+
9
+ errors = (@builder.error(name) if errors?) || BLANK
10
+
11
+ errors + content_tag(:div, options[:input]) do
12
+ has_many_fields_for(block) + has_many_links_for(block)
13
+ end
14
+ end
15
+
16
+ def input_html_options
17
+ { class: 'form-has-many mb-4' }
18
+ end
19
+
20
+ def input_js_options
21
+ { sortable: true }
22
+ end
23
+
24
+ def collection
25
+ Array(options[:input][:collection] || object.send(name))
26
+ end
27
+
28
+ # cards: false
29
+ def display
30
+ @display ||= (options[:input].delete(:cards) ? :cards : :rows)
31
+ end
32
+
33
+ # build: true
34
+ def build?
35
+ return @build unless @build.nil?
36
+
37
+ @build ||= begin
38
+ build = options[:input].delete(:build)
39
+ build.nil? ? true : build
40
+ end
41
+ end
42
+
43
+ # add: true
44
+ def add?
45
+ return @add unless @add.nil?
46
+
47
+ @add ||= begin
48
+ add = options[:input].delete(:add)
49
+ add.nil? ? true : add
50
+ end
51
+ end
52
+
53
+ # errors: true
54
+ def errors?
55
+ return @errors unless @errors.nil?
56
+
57
+ @errors ||= begin
58
+ errors = options[:input].delete(:errors)
59
+ errors.nil? ? true : errors
60
+ end
61
+ end
62
+
63
+ # remove: true
64
+ def remove?
65
+ return @remove unless @remove.nil?
66
+
67
+ @remove ||= begin
68
+ remove = options[:input].delete(:remove)
69
+
70
+ if remove != nil
71
+ remove
72
+ else
73
+ opts = (object.class.nested_attributes_options[name] || {})
74
+ opts[:update_only] != true && opts[:allow_destroy] != false
75
+ end
76
+ end
77
+ end
78
+
79
+ # reorder: true
80
+ def reorder?
81
+ return @reorder unless @reorder.nil?
82
+
83
+ @reorder ||= begin
84
+ reorder = options[:input].delete(:reorder)
85
+
86
+ if reorder != nil
87
+ reorder
88
+ else
89
+ build_resource().class.columns_hash['position']&.type == :integer
90
+ end
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def has_many_fields_for(block)
97
+ collection.map { |resource| render_resource(resource, block) }.join.html_safe
98
+ end
99
+
100
+ def has_many_links_for(block)
101
+ return BLANK unless add? || reorder?
102
+
103
+ content_tag(:div, class: 'has-many-links text-center mt-2') do
104
+ [*(link_to_add(block) if add?), *(link_to_reorder(block) if reorder?)].join(' ').html_safe
105
+ end
106
+ end
107
+
108
+ def render_resource(resource, block)
109
+ remove = BLANK
110
+
111
+ content = @builder.fields_for(name, resource) do |form|
112
+ fields = block.call(form)
113
+
114
+ remove += form.super_hidden_field(:_destroy) if remove? && resource.persisted?
115
+ remove += form.super_hidden_field(:position) if reorder? && !fields.include?('][position]')
116
+
117
+ fields
118
+ end
119
+
120
+ remove += link_to_remove(resource) if remove?
121
+
122
+ content_tag(:div, render_fields(content, remove), class: 'has-many-fields')
123
+ end
124
+
125
+ def render_fields(content, remove)
126
+ case display
127
+ when :rows
128
+ content_tag(:div, class: 'form-row') do
129
+ (reorder? ? content_tag(:div, has_many_move, class: 'col-auto') : BLANK) +
130
+ content_tag(:div, content, class: 'col mr-auto') +
131
+ content_tag(:div, remove, class: 'col-auto')
132
+ end
133
+ when :cards
134
+ raise('unsupported')
135
+ else
136
+ content + remove
137
+ end
138
+ end
139
+
140
+ def render_template(block)
141
+ resource = build_resource()
142
+ index = collection.length
143
+
144
+ html = render_resource(resource, block)
145
+
146
+ unless html.include?("#{name}_attributes][#{index}]")
147
+ raise('unexpected index. unable to render resource template.')
148
+ end
149
+
150
+ html.gsub!("#{name}_attributes][#{index}]", "#{name}_attributes][HASMANYINDEX]")
151
+ html.gsub!("#{name}_attributes_#{index}_", "#{name}_attributes_HASMANYINDEX_")
152
+
153
+ html.html_safe
154
+ end
155
+
156
+ def link_to_add(block)
157
+ content_tag(
158
+ :button,
159
+ icon('plus-circle') + 'Add Another',
160
+ class: 'has-many-add btn btn-secondary',
161
+ title: 'Add Another',
162
+ data: {
163
+ 'effective-form-has-many-add': true,
164
+ 'effective-form-has-many-template': render_template(block)
165
+ }
166
+ )
167
+ end
168
+
169
+ def link_to_reorder(block)
170
+ content_tag(
171
+ :button,
172
+ icon('list') + 'Reorder',
173
+ class: 'has-many-reorder btn btn-secondary',
174
+ title: 'Reorder',
175
+ data: {
176
+ 'effective-form-has-many-reorder': true,
177
+ }
178
+ )
179
+ end
180
+
181
+ def link_to_remove(resource)
182
+ content_tag(
183
+ :button,
184
+ icon('trash-2') + 'Remove',
185
+ class: 'has-many-remove btn btn-danger',
186
+ title: 'Remove',
187
+ data: {
188
+ 'confirm': "Remove #{resource}?",
189
+ 'effective-form-has-many-remove': true,
190
+ }
191
+ )
192
+ end
193
+
194
+ def has_many_move
195
+ @has_many_move ||= content_tag(:span, icon('move'), class: 'has-many-move')
196
+ end
197
+
198
+ def build_resource
199
+ # Using .new() here seems like it should work but it doesn't. It changes the index
200
+ @build_resource ||= object.send(name).build().tap do |resource|
201
+ object.send(name).delete(resource)
202
+ end
203
+ end
204
+
205
+ end
206
+ end
207
+ end
@@ -8,7 +8,7 @@ module EffectiveBootstrap
8
8
  end
9
9
 
10
10
  initializer 'effective_bootstrap.assets' do |app|
11
- app.config.assets.precompile += ['icons/*']
11
+ app.config.assets.precompile += ['effective_bootstrap_manifest.js', 'icons/*']
12
12
  end
13
13
 
14
14
  end
@@ -1,3 +1,3 @@
1
1
  module EffectiveBootstrap
2
- VERSION = '0.9.6'.freeze
2
+ VERSION = '0.9.11'.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.9.6
4
+ version: 0.9.11
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: 2020-09-29 00:00:00.000000000 Z
11
+ date: 2021-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: haml
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  description: Everything you need to get set up with bootstrap 4.
98
112
  email:
99
113
  - info@codeandeffect.com
@@ -103,6 +117,7 @@ extra_rdoc_files: []
103
117
  files:
104
118
  - MIT-LICENSE
105
119
  - README.md
120
+ - app/assets/config/effective_bootstrap_manifest.js
106
121
  - app/assets/images/icons/activity.svg
107
122
  - app/assets/images/icons/airplay.svg
108
123
  - app/assets/images/icons/alert-circle.svg
@@ -414,9 +429,13 @@ files:
414
429
  - app/assets/javascripts/effective_editor/image-resize.js
415
430
  - app/assets/javascripts/effective_editor/initialize.js.coffee
416
431
  - app/assets/javascripts/effective_editor/input.js
432
+ - app/assets/javascripts/effective_editor/magic-url.js
417
433
  - app/assets/javascripts/effective_editor/quill.js
418
434
  - app/assets/javascripts/effective_file/initialize.js.coffee
419
435
  - app/assets/javascripts/effective_file/input.js
436
+ - app/assets/javascripts/effective_has_many/initialize.js.coffee
437
+ - app/assets/javascripts/effective_has_many/input.js
438
+ - app/assets/javascripts/effective_has_many/jquery.sortable.js
420
439
  - app/assets/javascripts/effective_integer/initialize.js.coffee
421
440
  - app/assets/javascripts/effective_integer/input.js
422
441
  - app/assets/javascripts/effective_number_text/initialize.js.coffee
@@ -582,6 +601,7 @@ files:
582
601
  - app/assets/stylesheets/effective_editor/overrides.scss
583
602
  - app/assets/stylesheets/effective_editor/quill.scss
584
603
  - app/assets/stylesheets/effective_file/input.scss
604
+ - app/assets/stylesheets/effective_has_many/input.scss
585
605
  - app/assets/stylesheets/effective_radio/input.scss
586
606
  - app/assets/stylesheets/effective_rich_text_area/input.scss
587
607
  - app/assets/stylesheets/effective_select/bootstrap-theme.css
@@ -611,6 +631,7 @@ files:
611
631
  - app/models/effective/form_inputs/file_field.rb
612
632
  - app/models/effective/form_inputs/float_field.rb
613
633
  - app/models/effective/form_inputs/form_group.rb
634
+ - app/models/effective/form_inputs/has_many.rb
614
635
  - app/models/effective/form_inputs/hidden_field.rb
615
636
  - app/models/effective/form_inputs/integer_field.rb
616
637
  - app/models/effective/form_inputs/number_field.rb
@@ -666,7 +687,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
666
687
  - !ruby/object:Gem::Version
667
688
  version: '0'
668
689
  requirements: []
669
- rubygems_version: 3.1.4
690
+ rubygems_version: 3.1.2
670
691
  signing_key:
671
692
  specification_version: 4
672
693
  summary: Everything you need to get set up with bootstrap 4.